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

<template>
  <div id="app">
    <div class="title drag"> 
      
    
    <div class="right-top">
      <a class="a1 noDrag" @click="minimizeWin"></a>
      <a class="a3 noDrag" @click="closeWin"></a>
    </div>
    </div>
    <div class="drag"> 
      <img src="../assets/login/images/hys.png" alt="" />
    </div>

    <div id="dl">
      <h4>欢迎使用</h4>
      <ul class="tabs noDrag">
        <li
          class="li-tab"
          v-for="(item, index) in login.tabsParam"
          :key="index"
          @click="toggleTabs(index)"
          :class="{ active: index != login.nowIndex }"
        >
          {{ item }}
        </li>
      </ul>

      <div class="divTab noDrag" v-show="login.nowIndex === 0">
        <div class="">
          <form class="" id="">
            <div class="">
              <input
                type="text"
                class="input1"
                v-model="login.username"
                maxlength="10"
                placeholder="请输入您的用户名"
                id="username"
              />
            </div>
            <div class="">
              <input
                type="password"
                class="input1"
                maxlength="20"
                v-model="login.userpwd"
                placeholder="请输入密码"
                id="userpwd"
              />
            </div>
            <div class="">
              <div class="layui-input-block">
                <button
                  type="button"
                  :disabled="btnState == false"
                  :class="btnState == false ? 'loginBtndisable' : 'loginBtn'"
                  @click="doLogin"
                  v-preventReClick
                  v-on:keydown.enter="doLogin"
                >
                  登录
                </button>
              </div>
            </div>
          </form>
        </div>
      </div>
      <div class="divTab noDrag" v-show="login.nowIndex === 1">
        <div>
          <form class="" id="">
            <div class="">
              <input
                type="text"
                class="input1"
                v-model="login.mobile"
                maxlength="11"
                placeholder="请输入您的手机号"
                id="mobile"
              />
            </div>
            <div class="put2">
              <input
                type="text"
                name="userpwd"
                v-model="login.code"
                class="input2"
                maxlength="4"
                placeholder="请输入验证码"
                id="code"
              />
              <button
                type="button"
                :disabled="login.disabled"
                :class="login.disabled == true ? 'codedisable' : 'code'"
                @click="doCode"
                v-preventReClick
              >
                {{
                  !login.codeTime
                    ? "获取验证码"
                    : login.codeTime + "s" + "后获取"
                }}
              </button>
            </div>
            <div class="">
              <div class="layui-input-block">
                <button
                  type="button"
                  :disabled="codeState == false"
                  :class="codeState == false ? 'loginBtndisable' : 'loginBtn'"
                  @click="doCodeLogin"
                  v-preventReClick
                  @keyup.enter="doCodeLogin"
                >
                  登录
                </button>
              </div>
            </div>
          </form>
        </div>
      </div>
    </div>
  </div>
</template>

(2)、typescript

<script lang="ts">
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 < 1) {
                  clearInterval(timer);
                  login.codeTime = 0;
                  login.disabled = false;
                }
              }, 1000);
              login.disabled = true;
            } else {
              ElMessageBox.alert(res.msg, "提示");
            }
          });
      }
    };

    const doCodeLogin = () => {
      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,
    };
  },
};

</script>

(3)、css

<style>
.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;
}
</style>
<style lang="scss" scoped>
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;
}
</style>

六、路由-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').ipcRenderer
router.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])
    }

});

八、项目结构

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BOYKA®

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值