Практика разработки Electron-vue 1 — простая разработка основного процесса и процесса рендеринга

Vue.js Electron

предисловие

Некоторое время назад я использовалelectron-vueРазработано кросс-платформенное (в настоящее время поддерживающее Mac и Windows) бесплатное приложение для загрузки изображений с открытым исходным кодом——PicGo, я наступил на много ям в процессе разработки, причем не только со стороны бизнес-логики самого приложения, но и со стороны самого электрона. В процессе разработки этого приложения я многому научился. Поскольку я также начал изучать электрон с нуля, такой большой опыт также должен вдохновить и дать инструкции начинающим ученикам, которые хотят изучать разработку электронов. Поэтому я написал практический опыт разработки Электрона и объяснил его с точки зрения наиболее близкой к разработке реальных инженерных проектов. Надеюсь помочь всем.

Ожидается, что он будет расширен за счет нескольких серий статей или аспектов:

  1. Начало работы с электрон-вью
  2. Простая разработка основного процесса и процесса визуализации
  3. Представляем базу данных json на основе Lodash - lowdb
  4. Некоторые меры кроссплатформенной совместимости
  5. Выпуск и обновление через CI
  6. ... (подумайте о написании снова)

иллюстрировать

PicGoсостоит в том, чтобы принятьelectron-vueразработан, поэтому, если выvue, то учиться вместе будет быстрее. Если ваш технический стек похож наreact,angular, то чисто по этому туториалу, хотя вы можете и не много узнать о построении стороны рендера (которую можно понимать как страницу), вы все же должны получить соответствующие знания по основной стороне (основной процесс электрон).

Если вы не читали предыдущую статью, вы можете сначала прочитать ее из предыдущей статьи.

Базовое понимание основного процесса и процесса визуализации

Из конца предыдущей статьи мы успешно запустили электронно-вьюDEMOДавайте взглянем на поверхностное понимание этих двух процессов:

Вы можете видеть, что основной процесс управляет этим окном приложения (BrowserWindow), а процесс Renderer отвечает за отрисовку пользовательского интерфейса страницы, с которой мы знакомы. Но на самом деле их гораздо больше. На следующем рисунке можно примерно перечислить электронные или собственные модули, которые они поддерживают и которыми они управляют:

main & renderer process tree

Большинство модулей, перечисленных на рисунке, — это то, что мы будем использовать в процессе разработки.

У них есть свои собственные модули, а также общие модули, такие какclipboardЖдать. Другая часть — это модуль в процессе Main, но доступ к нему можно получить черезremoteМодуль, который позволяет процессу визуализации также использовать его. НапримерMenuНапримерshellЖдать.

Узнайте, какие модули находятся в каких процессах и какие модули доступны черезremoteЭто необходимо для того, чтобы модуль использовался процессом рендерера, чтобы мы могли корректно использовать его в последующей разработке.

Некоторые из вышеперечисленных модулей, возможно, не смогут увидеть, что это за функция из названия, это не имеет значения, последующий контент будет освещаться медленно.

Разработка основного процесса

Как упоминалось выше, значительная роль процесса Main заключается в создании окна приложения. Давайте посмотрим, как это достигается.

import { app, BrowserWindow } from 'electron' // 从electron引入app和BrowserWindow

let mainWindow

const winURL = process.env.NODE_ENV === 'development'
  ? `http://localhost:9080` // 开发模式的话走webpack-dev-server的url
  : `file://${__dirname}/index.html`

function createWindow () { // 创建窗口
  /**
   * Initial window options
   */
  mainWindow = new BrowserWindow({
    height: 563,
    useContentSize: true,
    width: 1000
  }) // 创建一个窗口

  mainWindow.loadURL(winURL) // 加载窗口的URL -> 来自renderer进程的页面

  mainWindow.on('closed', () => {
    mainWindow = null
  })
}

app.on('ready', createWindow) // app准备好的时候创建窗口

На данный момент, независимо от того, как выглядит страница в процессе рендеринга, открытие окна, когда приложение готово, требует только вызова метода создания.BrowserWindowможно использовать метод.

Разработка в основном процессе немного написана тогдаjQueryВнешний вид, более событийное письмо.

app

Обратите внимание, что первыйappМодуль. Этот модуль представляет собой скелетный электрон приложения. Он отвечает за хук всего жизненного цикла приложения, хук и многие другие события.

Общие крючки жизненного цикла приложения следующие:

  • will-finish-launchingЗапускается после того, как приложение завершило базовый процесс запуска.
  • readyЗапускается, когда электрон закончил инициализацию
  • window-all-closedСрабатывает при закрытии всех окон, в windows и linux при выходе из всех оконкак правилокогда приложение выходит
  • before-quitУволен перед выходом из приложения
  • will-quitЗапускается, когда приложение собирается выйти
  • quitЗапускается при выходе из приложения

и мы обычноreadyВыполнение операций инициализации, таких как создание окна приложения, создание меню приложения и создание сочетания клавиш приложения. пока вwill-quitилиquitПри выполнении некоторых операций очистки, таких как отвязка горячих клавиш приложения.

В частности, в АфрикеmacOSВ системе, как правило, когда закрываются все окна приложения, это также время выхода приложения. Так что можно сопоставитьwindow-all-closedЭтот крючок для достижения:

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') { // 当操作系统不是darwin(macOS)的话
    app.quit() // 退出应用
  }
})

В дополнение к хукам жизненного цикла, упомянутым выше, есть несколько часто используемых хуков событий:

  • active(только для macOS), когда приложение активно
  • browser-window-createdпри создании BrowserWindow
  • browser-window-focusКогда браузервинден

Эти крючки должны соответствовать ряду конкретных сценариев для выполнения определенных операций. При таком изменении BrowserWindow активное значение заголовка окна.

Конечно, в дополнение к некоторым из вышеперечисленных обработчиков событий модуль приложения также имеет несколько очень распространенных методов:

  • app.quit()выйти из приложения
  • app.getPath(name)Используется для получения некоторых системных каталогов, полезен для хранения файлов конфигурации приложений и т. д.
  • app.focus()Используется для активации приложений, различной логики активации системыРазные

Как известны эти события и методы? конечноофициальная документация. Тем не менее, нет необходимости сначала читать официальную документацию по API. Официальная документация по API больше используется для консультаций, когда вы хотите разработать функцию, вы можете проверить, есть ли у нее соответствующий API и как его использовать.

BrowserWindow

Модуль BrowserWindow используется для создания наиболее распространенных окон приложений. Для разных систем стили создаваемых окон по умолчанию также отличаются. Давайте посмотрим на разницу во внешнем виде окон между macOS и Windows:

версия для Mac

версия для Windows

Видно, что область действия (свернуть, развернуть, закрыть) и положение заголовка и меню в верхней части окна существенно различаются. Они такие же, как родные окна системы. Но если вы хотите украсить его, это нормально. Например:

PicGo для Mac

picgo-mac

и PicGo для окон

picgo-windows

Среди них версия для Mac использует операционную область системы, в то время как Windows не использует операционную область системы, а имитирует ее с помощью значков. Но там же не используется система по умолчаниюtitlebar. Это будет объединено позжеrendererобработать.

Давайте взглянем на общую конфигурацию для создания BrowserWindow:

let window

function createWindow () {
  window = new BrowserWindow({
    height: 900, // 高
    width: 400, // 宽
    show: false, // 创建后是否显示
    frame: false, // 是否创建frameless窗口
    fullscreenable: false, // 是否允许全屏
    center: true, // 是否出现在屏幕居中的位置
    backgroundColor: '#fff' // 背景色,用于transparent和frameless窗口
    titleBarStyle: 'xxx' // 标题栏的样式,有hidden、hiddenInset、customButtonsOnHover等
    resizable: false, // 是否允许拉伸大小
    transparent: true, // 是否是透明窗口(仅macOS)
    vibrancy: 'ultra-dark', // 窗口模糊的样式(仅macOS)
    webPreferences: {
      backgroundThrottling: false // 当页面被置于非激活窗口的时候是否停止动画和计时器
    }
    // ... 以及其他可选配置
  })

  window.loadURL(url)

  window.on('closed', () => { window = null })
}

Излишне говорить, что длина и ширина окна должны быть указаны. Некоторые из наиболее важных из них, на которые следует обратить внимание:frameЭта опция по умолчаниюtrue. если выбраноfalseframeless

const createSettingWindow = () => {
  const options = {
    height: 450,
    width: 800,
    show: false,
    frame: true,
    center: true,
    fullscreenable: false,
    resizable: false,
    title: 'PicGo',
    vibrancy: 'ultra-dark',
    transparent: true,
    titleBarStyle: 'hidden',
    webPreferences: {
      backgroundThrottling: false
    }
  }
  if (process.platform === 'win32') { // 针对windows平台做出不同的配置
    options.show = true // 创建即展示
    options.frame = false // 创建一个frameless窗口
    options.backgroundColor = '#3f3c37' // 背景色
  }
  settingWindow = new BrowserWindow(options)

  settingWindow.loadURL(settingWinURL)

  settingWindow.on('closed', () => {
    settingWindow = null
  })
}

appBrowserWindowЕсть также много часто используемых обработчиков событий:

  • closedкогда окно закрыто
  • focusкогда окно активировано
  • showкогда отображается окно
  • hideкогда окно скрыто
  • maxmizeкогда окно развернуто
  • minimizeкогда окно свернуто
  • ...

Конечно, практических методов еще много:

  • BrowserWindow.getFocusedWindow()[статический метод] Получить активное окно
  • win.close()[Метод экземпляра, то же самое ниже] Закройте окно
  • win.focus()активировать окно
  • win.show()окно дисплея
  • win.hide()скрыть окно
  • win.maximize()развернуть окно
  • win.minimize()свернуть окно
  • win.restore()Восстановить из свернутого окна
  • ...

Вам нужно выполнять разные операции в окне для разной бизнес-логики. Это должно соответствовать потребностям вашего проекта. Например, как упоминалось выше, рабочая область (увеличение, уменьшение, кнопка закрытия) в верхней части окна может быть реализована с помощью метода имитации значка + экземпляра.

Tray

Сначала вы можете не знать, что это такое. Это можно понять, чтобы понять компоненты значков на панели задач разных систем.

Например, в macOSTrayПосле сопоставления значка это значок приложения в верхней панели:

Например, в окнахTrayПосле сопоставления значка это значок приложения в правом нижнем углу Windows:

Следует отметить, что в Windows и macOS размер значка одинаков16*16Писать Икона верхней колонны под MacOS обычно ходит.黑白Маршрут, поэтому вы можете подготовить разные значки для обеих систем.PicGoвнутриTrayСгенерированный код примерно такой:

function createTray () {
  const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
  tray = new Tray(menubarPic) // 指定图片的路径
  // ... 其他代码
}

Обратите внимание, что в приведенном выше коде есть${__static}Переменные. Переменнаяelectron-vueДля нашего открытого корневого каталога проектаstaticПуть к папке. По этому пути каталог, в котором находятся ваши статические ресурсы, может быть хорошо расположен на этапах разработки и производства. очень удобная переменная.

КонечноTrayНе просто икона и ничего больше. Трей поддерживает множество полезных событий. Двумя наиболее важными из них являютсяclickа такжеright-click. Соответствует событиям щелчка левой и правой кнопки мыши соответственно.

событие щелчка левой кнопкой мыши

  • В системе macOS щелчок левой кнопкой мыши по значку в трее может вызвать меню конфигурации или окно приложения.
  • В Windows, щелчок левой кнопкой мыши на значке обычно появится окно приложения для лотка.

Клич правой кнопкой мыши

  • В macOS щелчок правой кнопкой мыши по значку в области уведомлений обычно вызывает меню конфигурации.
  • Под системой windows, то же, что и выше.

Поэтому нам необходимо адаптироваться к привычкам работы пользователей в разных операционных системах.

В соответствии с PicGo, щелчок левой кнопкой мыши в системе macOS откроет небольшое окно строки меню, а щелчок правой кнопкой мыши откроет меню конфигурации. В Windows главное окно появится сразу при щелчке левой кнопкой мыши (поскольку в Windows нет необходимости в маленьких окнах), а меню конфигурации появится при щелчке правой кнопкой мыши. В PicGo они реализованы следующим образом:

function createTray () {
  const menubarPic = process.platform === 'darwin' ? `${__static}/menubar.png` : `${__static}/menubar-nodarwin.png`
  tray = new Tray(menubarPic)
  const contextMenu = // ...菜单
  tray.on('right-click', () => { // 右键点击
    window.hide() // 隐藏小窗口
    tray.popUpContextMenu(contextMenu) // 打开菜单
  })
  tray.on('click', () => { // 左键点击
    if (process.platform === 'darwin') { // 如果是macOS
      toggleWindow() // 打开或关闭小窗口
    } else { // 如果是windows
      window.hide() // 隐藏小窗口
      if (settingWindow === null) { // 如果主窗口不存在就创建一个
        createSettingWindow()
        settingWindow.show()
      } else { // 如果主窗口在,就显示并激活
        settingWindow.show()
        settingWindow.focus()
      }
    }
  })
}

Для macOS в трее также есть отличная функция — вы можете перетаскивать файлы на значок в трее, что вызовет следующие события:

  • dropкогда что-то перетаскивается на иконку
  • drop-filesКогда файл перетаскивается на значок
  • drop-textкогда текст перетаскивается на иконку
  • drop-enterКогда просто перетащили на иконку
  • drop-leaveКогда событие перетаскивания покидает значок
  • drop-endКогда событие перетаскивания заканчивается

Так же, как функция загрузки изображений, реализованная PicGo при перетаскивании изображений на значок в трее, используются некоторые из вышеперечисленных событий:

Обратите особое внимание на то, что значок отличается, когда перетаскивание включено и когда перетаскивание закончилось. Это реализовано в PicGo, очень просто:

  tray.on('drag-enter', () => {
    tray.setImage(`${__static}/upload.png`)
  })

  tray.on('drag-end', () => {
    tray.setImage(`${__static}/menubar.png`)
  })

а такжеTrayЕще одна важная роль — открыть пункт меню. Это будет объединено со следующим разделомMenuОбъясните вместе.

Menu

Мощный компонент «Меню» электрона может не только генерировать пункты системного меню, но и реализовывать функцию привязки общих клавиш быстрого доступа приложений.

Давайте сначала посмотрим, что представляют собой пункты системного меню:

macOS

windows

В основном есть два типа.

  • Во-первых, это меню приложения. Для macOS это пункт меню в левой части верхней панели. Для окон это область меню под строкой заголовка окна.
  • Второе меню похоже на контекстное меню.

В первое меню можно попасть черезMenu.setApplicationMenu()реализовать.

Второе меню можно отобразить в два этапа:

1.Создайте меню:

 const contextMenu = Menu.buildFromTemplate([...])

2.Показать меню:

tray.on('right-click', () => { // 右键点击tray的时候
  tray.popUpContextMenu(contextMenu) // 弹出菜单
})

Здесь мы только вводимMenuсам. На самом деле составMenuпо одномуMenuItem. Они бывают многих видов:

  1. normal
  2. separator
  3. submenu
  4. checkbox
  5. radio

и много ролей:

  1. quit
  2. copy
  3. redo
  4. undo
  5. minimize
  6. close
  7. reload
  8. ...

Вообще говоря, пункты меню конфигурации в основном сгруппированы по типу. Например, пункты меню PicGo:

Есть четыре типа нормальных, подменных, флажков и радио. По умолчанию нормально.

Слова-символы обычно соответствуют некоторому общему поведению. Напримерquitвыйти из приложения, напримерminimizeсводится к минимуму, напримерcopyявляется копией. Однако следует отметить, что если вы не укажете сочетания клавиш для этих действий в меню «Создать приложение», то некоторые распространенные сочетания клавиш нельзя будет использовать в вашем приложении. Напримерctrl+cилиcommand+cДублируйте эту операцию, если вы не проходитеMenu.setApplicationMenu()Если вы установите эту горячую клавишу, то операция копирования не может быть выполнена в вашем электронном приложении. PicGo также делал это в более ранних версиях.ошибка. Проблема в то время заключалась в том, что у меня не было проблем в режиме разработки, но копирование-вставка была невозможна в рабочем режиме. Позже я проверил причину и обнаружил, что в режиме разработки электрон поставит некоторые контекстные меню по умолчанию, как показано на рисунке:

Таким образом, в производственном режиме, если я не добавлю эти сочетания клавиш, пользователь не сможет их использовать.это большая дыра.

Сказав это, давайте посмотрим, как выглядит код, который генерирует меню приложения:

Обратите внимание, что если вы используете только следующие сочетания клавиш непосредственно в режиме разработки, некоторые сочетания клавиш отладки, такие какF12илиcommand+shift+iОперация открытия консоли работать не будет. Поэтому нет необходимости создавать эти контекстные меню в режиме разработки.

const createMenu = () => {
  if (process.env.NODE_ENV !== 'development') {
    const template = [{
      label: 'Edit',
      submenu: [
        { label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' },
        { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' },
        { type: 'separator' },
        { label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' },
        { label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' },
        { label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' },
        { label: 'Select All', accelerator: 'CmdOrCtrl+A', selector: 'selectAll:' },
        {
          label: 'Quit',
          accelerator: 'CmdOrCtrl+Q',
          click () {
            app.quit()
          }
        }
      ]
    }]
    menu = Menu.buildFromTemplate(template)
    Menu.setApplicationMenu(menu)
  }
}

в состоянии пройтиacceleratorУкажите нужную комбинацию клавиш. Такие какShift,Ctrl,CmdЭквивалентная аббревиатура ключа. Если это комбинация клавиш, добавьте+.尤其注意到,因为macOS和windows键位的差异,所以有一个很好用的键位缩写CmdOrCtrl, т. е. если бы на macOS было быCmd, на окнах этоCtrl.

Затем посмотрите на генерацию контекстного меню Tray:

 const contextMenu = Menu.buildFromTemplate([
    {
      label: '关于',
      click () {
        dialog.showMessageBox({
          title: 'PicGo',
          message: 'PicGo',
          detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
        })
      }
    },
    {
      label: '打开详细窗口',
      click () {
        if (settingWindow === null) {
          createSettingWindow()
          settingWindow.show()
        } else {
          settingWindow.show()
          settingWindow.focus()
        }
      }
    },
    {
      label: '选择默认图床',
      type: 'submenu',
      submenu: [
        {
          label: '微博图床',
          type: 'radio',
          checked: db.read().get('picBed.current').value() === 'weibo',
          click () {
            db.read().set('picBed.current', 'weibo')
              .write()
          }
        },
        {
          label: '七牛图床',
          type: 'radio',
          checked: db.read().get('picBed.current').value() === 'qiniu',
          click () {
            db.read().set('picBed.current', 'qiniu')
              .write()
          }
        },
        {
          label: '腾讯云COS',
          type: 'radio',
          checked: db.read().get('picBed.current').value() === 'tcyun',
          click () {
            db.read().set('picBed.current', 'tcyun')
              .write()
          }
        },
        {
          label: '又拍云图床',
          type: 'radio',
          checked: db.read().get('picBed.current').value() === 'upyun',
          click () {
            db.read().set('picBed.current', 'upyun')
              .write()
          }
        }
      ]
    },
    {
      label: '打开更新助手',
      type: 'checkbox',
      checked: db.get('picBed.showUpdateTip').value(),
      click () {
        const value = db.read().get('picBed.showUpdateTip').value()
        db.read().set('picBed.showUpdateTip', !value).write()
      }
    },
    {
      role: 'quit',
      label: '退出'
    }
  ])

  tray.on('right-click', () => {
    tray.popUpContextMenu(contextMenu)
  })

Обратите внимание, что событие click элемента меню может быть напрямую передано черезclickсвойства указать. Мы прошли первымиMenu.buildFromTemplate()Этот метод создает меню, а затем щелкает правой кнопкой мышиTrayКогда значок отобразится, поднимите его (PopUp).

Конечно, есть и другие способы построения меню. Доступно через экземпляр менюappendспособ присоединитьсяMenu Item. Например:

const menu = new Menu()
menu.append(new MenuItem({ label: 'Cut', accelerator: 'CmdOrCtrl+X' }))
menu.append(new MenuItem({ type: 'separator' })) // 分割线
menu.append(new MenuItem({ label: 'Helper', type: 'checkbox', checked: true }))

По сути, с помощью вышеуказанных базовых модулей в основном построен скелет одного из наших приложений с окнами, значками приложений на панели задач и элементами меню. Другие модули основного процесса не нужны и будут постепенно упоминаться в последующих статьях по мере их использования. В следующем разделе мы рассмотрим разработку процесса визуализации.

Разработка процесса визуализации

дляelectron-vueПо большей части процесс рендеринга на самом деле пишет интерфейсные страницы, которые мы обычно пишем. Однако по сравнению со страницами, которые вы обычно пишете в браузере, при написании страниц в электронном виде вы по-прежнему можете использовать множество небраузерных модулей, таких какfs, такие как электрон черезremoteМодули модули, открытые для процесса рендеринга. Далее, давайте посмотрим, на что должен обращать внимание процесс рендеринга.

Пожалуйста, используйте хеш-режим

Обычно, когда мы пишем Vue, мы предпочитаем открытую маршрутизацию.historyрежим, потому что он лучше выглядит в адресной строке браузера - без хэша#Номер - это просто URL-адрес задней части. Однако следует отметить, чтоhistoryСхема требует поддержки внутреннего сервера.

Возможно, многие друзья не чувствуют этого, когда обычно разрабатывают, потому что vue-cli запускается в режиме разработки.webpack-dev-serverпомочь вам реализовать серверhistory-fallbackхарактеристики. Таким образом, при фактическом развертывании вам, по крайней мере, необходимо использовать программы веб-сервера, такие какnginx,apacheНастройте соответствующие правила и дайте интерфейсному маршруту вернуться кvue-routerиметь дело с.

И то же самое верно и для электрона. В режиме разработки, посколькуwebpack-dev-serverсервер включен, поэтомуBrowserWindowЗагружается с чего-то вроде ``http://localhost:9080这样的地址的页面。而在生产模式下,却是使用的file://的协议,比如file://${__dirname}/index.html`, чтобы указать страницу, загружаемую окном.

Таким образом, вы можете понять из приведенного выше выражения. Предположим, у меня есть подмаршрут с адресомchild. Если режим хеширования не включен, в режиме разработки проблем нет.http://localhost:9080/child, но в производственном режиме,file://${__dirname}/index.html/childЭто путь, который не может быть согласован. Итак, под электрономvue-routerпожалуйста, не используйтеhistoryрежим и использовать по умолчаниюhashмодель.

Тогда вышеуказанная проблема может быть легко решена, и она станетfile://${__dirname}/index.html#childВот и все.

PicGo в правилах маршрутизации загрузки страницы следующие, из которых вы можете видеть, что я используюhashмодель.

const winURL = process.env.NODE_ENV === 'development'
  ? `http://localhost:9080`
  : `file://${__dirname}/index.html`
const settingWinURL = process.env.NODE_ENV === 'development'
  ? `http://localhost:9080/#setting/upload`
  : `file://${__dirname}/index.html#setting/upload`

Реализуйте свой собственный заголовок

говорить вышеBrowserWindowКогда я сказал, что иногда для красоты приложения я не хочу, чтобы наше окно приложения использовало системное значение по умолчанию.titlebar, и хотите использовать свои собственные написанные для достижения. Это создание вашегоBrowserWindowДобавьте предложение в конфигурацию

titleBarStyle: 'hidden'

хорошо. Затем вы можете самостоятельно смоделировать верхнюю часть страницы процесса рендеринга.titlebar, как указано вышеPicGoизtitlebarСмотреть. На самом деле код тоже очень простой:

<div class="fake-title-bar">
  PicGo - {{ version }}
  <div class="handle-bar" v-if="os === 'win32'"> <!-- 如果是windows系统 就加上模拟的操作按钮-->
    <i class="el-icon-minus" @click="minimizeWindow"></i>
    <i class="el-icon-close" @click="closeWindow"></i>
  </div>
</div>

Затем поместите положение этой строки заголовка сверху.

Однако при обычном использовании мы должны обратить внимание на то, что обычно мы можем перетаскивать окно, удерживая мышью строку заголовка. Но если мы не добавим атрибут draggable, в заголовке, который мы написали сами, такой возможности нет. Добавить эту функцию также легко:

.fake-title-bar {
  -webkit-app-region drag
}

Сделайте заголовок перетаскиваемым с помощью всего одной строки CSS.

Однако под Windows кнопку (уменьшение, масштабирование и закрытие) долго тащить не стоит, поэтому для нее тоже нужно:

.handle-bar {
  -webkit-app-region no-drag
}

сталиno-drag, который реализует заголовок нашего собственного сгенерированного приложения.

Предотвращение перетаскивания

Обычно, когда мы используем Chrome, есть функция, благодаря которой, если вы перетащите PDF-файл в Chrome, он автоматически откроется с помощью встроенной программы чтения PDF-файлов. Вы перетаскиваете изображение в Chrome, и оно открывается. Благодаря нашему электронному приложениюBrowserWindowПо сути, это также браузер внутри, поэтому эта функция все еще существует. И на это многие не обращают внимания. То есть после того, как вы разработаете электронное приложение, перетащите картинку, pdf и т. д., если это не перетаскиваемая область (например, область загрузки PicGo), то она не должна открывать эту картинку, этот pdf, а исключать Это.

Так что будем слушать глобальноdragа такжеdropСобытие, когда пользователь перетаскивает файл, но не в область перетаскивания, он должен быть заблокирован. Потому что все страницы должны иметь такие функции, поэтому я написал vuemixin:

export default {
  mounted () {
    this.disableDragEvent()
  },
  methods: {
    disableDragEvent () {
      window.addEventListener('dragenter', this.disableDrag, false)
      window.addEventListener('dragover', this.disableDrag)
      window.addEventListener('drop', this.disableDrag)
    },
    disableDrag (e) {
      const dropzone = document.getElementById('upload-area') // 这个是可拖拽的上传区
      if (dropzone === null || !dropzone.contains(e.target)) {
        e.preventDefault()
        e.dataTransfer.effectAllowed = 'none'
        e.dataTransfer.dropEffect = 'none'
      }
    }
  },
  beforeDestroy () {
    window.removeEventListener('dragenter', this.disableDrag, false)
    window.removeEventListener('dragover', this.disableDrag)
    window.removeEventListener('drop', this.disableDrag)
  }
}

Таким образом, этот миксин может быть представлен глобально.

Использование удаленного модуля

Удаленный модуль создается электроном, чтобы позволить некоторым модулям, которые изначально работали в процессе Main, выполняться в процессе рендерера. Вот некоторые из них, которые мы будем использовать.

существуетelectron-vueвстроенныйvue-electronЭтот модуль можно легко использовать в vue, напримерthis.$electron.remote.xxxиспользовать удаленный модуль.

shell

shellОфициальное описание модуля:Manage files and URLs using their default applications.То есть приложение по умолчанию, которое использует файл или URL-адрес. Обычно мы можем использовать его, чтобы приложение для работы с изображениями по умолчанию открывало изображение, а браузер по умолчанию открывал URL-адрес.

Если мы хотим нажать кнопку в процессе рендеринга и открыть URL-адрес в браузере по умолчанию, мы можем сделать это:

<button @click="openURL"></button>

<script>
  export default {
    methods: {
      openURL () {
        this.$electron.remote.shell.openExternal('https://github.com/Molunerfinn/PicGo')
      }
    }
  }
</script>

Это удобно?

Более подробное использование оболочки может относиться кДокументация.

dialog

Иногда нам нужно открыть родное диалоговое окно. НапримерPicGoинформация о версии:

macOS

windows

В это время можно пройтиdialogЭтот модуль делает это. Та же логика, что и выше, заключается в том, чтобы нажать кнопку, чтобы открыть диалоговое окно:

openDialog () {
  this.$electron.remote.dialog.showMessageBox({
    title: 'PicGo',
    message: 'PicGo',
    detail: `Version: ${pkg.version}\nAuthor: Molunerfinn\nGithub: https://github.com/Molunerfinn/PicGo`
  })
}

Для получения более подробной информации об использовании диалога см.Документация.

Применение меню и BrowserWindow

использоватьMenuМногие люди могут быть в состоянии понять. Но зачем использоватьBrowserWindowШерстяная ткань? Потому что вам нужно найти вас открытьMenuокно.

В PicGo есть кнопка, чтобы открыть меню, которое примерно выглядит следующим образом:

buildMenu () {
    const template = [...]
    this.menu = Menu.buildFromTemplate(template)
  },
  openDialog () {
    this.menu.popup(remote.getCurrentWindow) // 获取当前打开Menu的窗口
  }

здесьmenu.popupВам нужно указать окно, чтобы открыть это меню. Он автоматически обнаружит, где вы нажали, и появится всплывающее окно.

Связь между основным процессом и процессом рендерера

В Vue, если это не взаимодействие между родительским и дочерним компонентами, оно обычно используется черезBus Eventбыть реализованным. Связь между различными процессами в электроне на самом деле очень похожа, черезipcMainа такжеipcRendererбыть реализованным. вipcMainвmainиспользуется в процессе иipcRendererвrendererиспользуется в процессе.

ipcMain и ipcRenderer

Пример на официальном сайте на самом деле очень лаконичен и понятен, я его выложил:

// In main process.
const {ipcMain} = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
  console.log(arg)  // prints "ping"
  event.sender.send('asynchronous-reply', 'pong')
})

ipcMain.on('synchronous-message', (event, arg) => {
  console.log(arg)  // prints "ping"
  event.returnValue = 'pong'
})
// In renderer process (web page).
const {ipcRenderer} = require('electron')
console.log(ipcRenderer.sendSync('synchronous-message', 'ping')) // prints "pong"

ipcRenderer.on('asynchronous-reply', (event, arg) => {
  console.log(arg) // prints "pong"
})
ipcRenderer.send('asynchronous-message', 'ping')

вipcMainмониторить только изipcRendererможет быть возвращен только после событияipcRendererстоимость. а такжеipcRendererМожно получить или отправить.

Тогда возникает вопрос, как сделатьipcMainКак насчет проактивной отправки сообщений? Или пусть основной процесс активно отправляет сообщенияipcRenderer.

Первое, что нужно уяснить, это то,ipcMainНевозможно проактивно отправить сообщениеipcRenderer. Поскольку ipcMain имеет только.on()метод №.send()Методы. Так что это можно сделать только другими способами. Есть ли способ? да, использоватьwebContents.

webContents

webContentsФактическиBrowserWindowАтрибут экземпляра. То есть, если нам нужноmainЧтобы отправить сообщение окну или странице в процессе, вы должны передатьwin.webContents.send()способ отправки.

Код примерно такой:

// In main process
let win = new BrowserWindow({...})
win.webContents.send('img-files', imgs)
// In renderer process
ipcRenderer.on('img-files', (event, files) => {
  console.log(files)
})

Следовательно, окно для отправки должно быть указано, чтобы информация могла быть доставлена ​​точно.

Суммировать

В этой статье подробно описывается электронMainпроцесс иRendererБазовые знания о процессах имеют отношение к разработке. я много развиваюсьPicGoВозникшие проблемы и ямы, которые были наступлены. Возможно, за простыми предложениями в тексте - мои бесчисленные чтения и отладки. Содержание намного больше, чем первый, я надеюсь, что эта статья может дать вамelectron-vueРазвитие приносит некоторое вдохновение. Соответствующий код в тексте, вы можете найти его вPicGoНайдено в репозитории проекта, добро пожаловать, нажмите звездочку ~ Надеюсь, эта статья поможет вам, это мое самое счастливое место. Если вам это нравится, добро пожаловать в мой блог и следите за ходом этой серии статей.

Примечание: фотографии в этой статье принадлежат моей личной работе, если не указано иное, если вам нужно перепечатать, пожалуйста, отправьте личное сообщение