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
(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]) }});
八、项目结构