> 文档中心 > Electron Vue3 开发笔记(三)

Electron Vue3 开发笔记(三)


今天开始配置一个完整的项目,如何搭建一个登录页面。登录页面采用ui部分为自定义,没有采用element-plus。该项目背景透明、CSS圆角、标题栏和菜单全部隐藏,采用自定义“关闭”和“最小化”按钮(主进程与渲染进程之间通信)、自定义桌面图标、r任务栏图标等。

Electron分为主进程和渲染进程:

主进程:每个 Electron 应用程序都有一个主进程,作为应用程序的入口点。主进程在 Node.js 环境中运行,这意味着它能够require模块化和使用所有 Node.js API。主要目的是使用 BrowserWindow模块创建和管理应用程序窗口。

渲染进程:由于 Electron 使用 Chromium 来展示页面。每个 Electron 的页面都在运行着自己的进程,这样的进程我们称之为渲染进程。也可以理解为每创建一个 web 页面都会创建一个渲染进程,每个 web 页面都运行在它自己的渲染进程中,每个渲染进程是独立的,它只关心它所运行的页面。

 

一、基于vite初始化vue3+typescript项目

yarn create vite electron-project --template vue-ts

二、安装element-plus

yarn add element-plus

三、安装 concurrently cross-env electron-builder wait-on

 yarn add --dev concurrently  cross-env electron-builder wait-on

四、配置调试脚本

  "scripts": {    "electron:serve": "concurrently -k \"yarn dev\" \"yarn electron\"",    "electron:build": "vite build && electron-builder",    "dev": "vite",    "build": "vue-tsc --noEmit && vite build",    "preview": "vite preview",    "electron": "wait-on tcp:4000 && cross-env NODE_ENV=development electron ."  },

五、登录页面-login.html

(1)、html

  
Electron Vue3 开发笔记(三)

欢迎使用

  • {{ item }}

(2)、typescript

import router from "../router";import { ElMessageBox } from "element-plus";const { ipcRenderer: ipc } = require("electron");import {  computed,  ComponentInternalInstance,  getCurrentInstance,  reactive,} from "vue";export default {  setup(props, context) {    const login = reactive({      username: "",      userpwd: "",      mobile: "",      code: "",      nowIndex: 0,      codeTime: 0,      buttondisabled: true,      disabled: false,      tabsParam: ["SE账号", "SE手机号"],    });    const btnState = computed(() => {      return login.username !== "" && login.userpwd !== "";    });    const codeState = computed(() => {      return login.mobile !== "" && login.code !== "";    });    const { proxy } = getCurrentInstance() as ComponentInternalInstance;    const doLogin = () => {      proxy?.$axios .post("/admin/office/login", {   username: login.username,   userpwd: login.userpwd, }) .then((res) => {   if (res.code == 1) {     localStorage.setItem("access-token", res.access_token);     localStorage.setItem("uid", res.uid);     localStorage.setItem("username", res.username);     localStorage.setItem("unid", res.units[0].unid);     localStorage.setItem("unitname", res.units[0].unitname);     ipc.send("login");     router.push({path: "/main",     });   } else {     ElMessageBox.alert(res.msg, "提示",{});   } });    };    const doCode = () => {      if (login.codeTime > 0) { return;      } else { proxy?.$axios   .post("/admin/office/sendSms", {     mobile: login.mobile,   })   .then((res) => {     if (res.code == 1) {ElMessageBox.alert("短信发送成功!", "提示");login.codeTime = 5;let timer = setInterval(() => {  login.codeTime--;  if (login.codeTime  {      proxy?.$axios .post("/admin/office/smsLogin", {   mobile: login.mobile,   code: login.code, }) .then((res) => {   if (res.code == 1) {     localStorage.setItem("access-token", res.access_token);     localStorage.setItem("uid", res.uid);     localStorage.setItem("username", res.username);     localStorage.setItem("unid", res.units[0].unid);     localStorage.setItem("unitname", res.units[0].unitname);     ipc.send("login");     router.push({path: "/main",     });   } else {     ElMessageBox.alert(res.msg, "提示");   } });    };    const toggleTabs = (index: any) => {      login.nowIndex = index;    };    const minimizeWin = () => {      ipc.send("min");    };    const maximizeWin = () => {      ipc.send("max");    };    const closeWin = () => {      ipc.send("close");    };    return {      doLogin,      toggleTabs,      minimizeWin,      maximizeWin,      closeWin,      login,      doCode,      btnState,      codeState,      doCodeLogin,    };  },};

(3)、css

.el-overlay {    position: fixed;    top: 0;    right: 0;    bottom: 0;    left: 0;    z-index: 2000;    height: 100%;    background-color: rgb(0 0 0 / 0%);    overflow: auto;}ul li {  list-style: none;}a {  text-decoration: none;}#app {  width: 950px;  height: 630px;  display: flex;  margin-left: 5px;  margin-top: 5px;  justify-content: space-between;  align-items: center;  background: #f2f3f5;  box-sizing: border-box;  position: relative;  -moz-box-shadow:0px 0px 4px #6d6d6d;  -webkit-box-shadow:0px 0px 4px #6d6d6d;  box-shadow:0px 0px 4px #6d6d6d;  border-radius: 4px;}.drag{-webkit-app-region: drag;}.noDrag {  -webkit-app-region: no-drag;}#app > div > img {  margin-left: 100px;}#dl {  margin-right: 40px;}.title {  position: absolute;  top: 0px;  right: 0px;  width: 100%;  height: 50px;  display: flex;  // background-color: #dcddee;  justify-content: space-between;}.right-top {  position: absolute;  top: 8px;  right: 8px;  width: 70px;  height: 34px;  display: flex;  justify-content: space-between;}.right-top a {  width: 34px;  height: 34px;}.right-top .a1 {  background: url(../assets/login/images/pick1.png) no-repeat center center #f2f3f5;}.right-top .a1:hover {  background: url(../assets/login/images/pick4.png) no-repeat center center #e6e8eb;}.right-top .a3 {  background: url(../assets/login/images/pick3.png) no-repeat center center #f2f3f5;}.right-top .a3:hover {  background: url(../assets/login/images/pick6.png) no-repeat center center #f44a45;}#dl {  width: 400px;  height: 512px;  background: #ffffff;  border-radius: 6px;}#dl > h4 {  font-size: 22px;  margin: 59px 0 0 29px;}#dl > ul {  margin: 30px 0 0 30px;  height: 40px;}#dl > ul li {  float: left;  margin-right: 29px;  font-size: 16px;  border-bottom: #1677ff 2px solid;  padding-bottom: 5px;  color: #1677ff;  cursor: pointer;}#dl > ul li.active {  border-bottom: 0;  color: #8e949d;}.bd > div {  display: none;}.bd > div.active {  display: block;}.bd .input1 {  width: 340px;  height: 50px;  border-radius: 6px;  background: #ffffff;  border: 1px solid #cfd2d5;  margin: 20px auto 0;  display: block;  font-size: 16px;  padding-left: 20px;  box-sizing: border-box;}.input1 {  width: 340px;  height: 50px;  border-radius: 6px;  background: #ffffff;  border: 1px solid #cfd2d5;  margin: 20px auto 0;  display: block;  font-size: 16px;  padding-left: 20px;  box-sizing: border-box;}.loginBtn {  display: block;  margin: 40px auto 30px;  width: 340px;  height: 50px;  background: #1677ff;  border-radius: 25px;  border: 0;  font-size: 16px;  color: #fff;}.loginBtndisable {  display: block;  margin: 40px auto 30px;  width: 340px;  height: 50px;  background: #bbbfc4;  border-radius: 25px;  border: 0;  font-size: 16px;  color: #fff;  cursor: not-allowed;}.put2 {  width: 340px;  margin: 20px auto 0px;  display: flex;  justify-content: space-between;}.input2 {  width: 200px;  height: 50px;  background: #ffffff;  border: 1px solid #cfd2d5;  border-radius: 6px;  font-size: 16px;  padding-left: 20px;  box-sizing: border-box;}$color-code-disable-false: #1677ff;$color-code-disable-true: #c0c0c0;.code {  width: 120px;  height: 50px;  background: $color-code-disable-false;  border-radius: 6px;  color: #fff;  border: 0;  cursor: pointer;}.codedisable {  width: 120px;  height: 50px;  background: $color-code-disable-true;  border-radius: 6px;  color: #fff;  border: 0;  cursor: not-allowed;}label {  display: block;  margin: 0 0 24px 30px;  color: #8e949d;  font-size: 14px;}label input {  margin-right: 10px;}label a {  color: #1677ff;}

六、路由-router.js

import {createRouter, createWebHashHistory} from 'vue-router';import login from "../view/login.vue"const routes = [    { path: '/', redirect: '/login'    },    { path: '/login', name: 'login', component: login    }  ]const router = createRouter({    history: createWebHashHistory(),    routes})const ipc = require('electron').ipcRendererrouter.beforeEach((to, from, next) => {    if (to.path === '/login') return next()    const token = localStorage.getItem('access-token')    if (!token) { ipc.send("relaunch");    }    next()  })export default router;

七、入口-main.js

const { app, BrowserWindow, dialog, globalShortcut } = require('electron')const electron = require('electron');const path = require('path')const ipc = require('electron').ipcMain;const Menu = electron.Menu;const Tray = electron.Tray;var appTray = null;process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true';ipc.on('new-window', function () {  mainWindow.loadURL(url.format({    pathname: path.join(__dirname, '../dist/main.html'),    protocol: 'file:',    slashes: true  }))})const NODE_ENV = process.env.NODE_ENV;const clearObj = {  storages: ['appcache', 'filesystem', 'localstorage', 'shadercache', 'websql', 'serviceworkers', 'cachestorage']};async function createWindow() {  const mainWindow = new BrowserWindow({    frame: false,    hasShadow: false,    transparent: true,    backgroundColor: '#00000000',    width: 960,    height: 640,    useContentSize: true,    resizable: false,    show: false,    icon: 'src/assets/icon/fav256.ico',    webPreferences: {      preload: path.join(__dirname, 'preload.js'),      enableRemoteModule: true,      nodeIntegration: true,      webSecurity: false,      contextIsolation: false    }  })  mainWindow.setAppDetails({    appId: "com.successkaoyan",    appIconPath: "./src/assets/icon/fav256.ico",    appIconIndex: 0,    relaunchCommand: "Meeting Room",    relaunchDisplayName: "Meeting Room",  })  ipc.on('login', () => {    mainWindow.setSize(1260, 750);    mainWindow.center();  })  const gotTheLock = app.requestSingleInstanceLock()  if (!gotTheLock) {    app.quit()  } else {    app.on('second-instance', (event, commandLine, workingDirectory) => {      if (mainWindow) { if (mainWindow.isMinimized()) mainWindow.restore() mainWindow.focus() mainWindow.show()      }    })  }  ipc.on('min', e => mainWindow.minimize());  ipc.on('max', e => {    if (mainWindow.isMaximized()) {      mainWindow.unmaximize()    } else {      mainWindow.maximize()    }  });  ipc.on('close', e => mainWindow.hide());  ipc.on('activate', e => {    mainWindow.setFullScreen(true);    mainWindow.maximize()  });  ipc.on('relaunch', e => {    app.relaunch(); app.exit();  })  var trayMenuTemplate = [    {      label: '退出',      click: function () { mainWindow.webContents.session.clearStorageData(clearObj); app.quit();      }    }  ];  mainWindow.on("close",()=>{    mainWindow.webContents.session.clearStorageData(clearObj);  })  if (NODE_ENV === "development") {    trayIcon = path.join(app.getAppPath(), 'src/assets/icon/fav32.ico');    mainWindow.webContents.openDevTools()  } else {    trayIcon = path.join(__dirname, 'fav32.ico');  }  appTray = new Tray(trayIcon);  const contextMenu = Menu.buildFromTemplate(trayMenuTemplate);  appTray.setToolTip('Meeting Room');  appTray.setContextMenu(contextMenu);  appTray.on('click', function () {    mainWindow.show();  })  mainWindow.setMenu(null);  await mainWindow.loadURL(    NODE_ENV === "development" ?      "http://localhost:3000" :      `file://${path.join(__dirname, "../dist/index.html")}`  )  mainWindow.show()  globalShortcut.register('CommandOrControl+Shift+i', function () {    mainWindow.webContents.openDevTools()  })  globalShortcut.register('CommandOrControl+T', () => {    mainWindow.maximize()  })  globalShortcut.register('CommandOrControl+M', () => {    mainWindow.unmaximize()  })  globalShortcut.register('CommandOrControl+H', () => {    mainWindow.close()  })}app.whenReady().then(() => {  createWindow()})app.on('window-all-closed', () => {  if (process.platform !== 'darwin') {    app.quit()  }})const gotTheLock = app.requestSingleInstanceLock()if (!gotTheLock) {  app.quit()}app.disableHardwareAcceleration()

七、预加载-preload.js

window.addEventListener("DOMContentLoaded",() => {    const replaceText = (selector,text) =>{ const element = document.getElementById(selector); if (element) element.innerText = text;    };    for(const dependency of ["chrome","node","electron"]){ replaceText(`${dependency}-version`,process.versions[dependency])    }});

八、项目结构