1、使用vuecli创建vue项目
我用的vue2
vue create qq_test
2、安装electron
npm install electron -g
//or
npm install electron@12.0.11 //老版本
3、vue项目安装Electron-builder打包工具
版本我选择的是12
vue add electron-builder
4、在vue项目的src下有个background.js文件
background.js里面有默认的配置,修改后我的配置大概如下
'use strict'
import { app, protocol, BrowserWindow, Menu, Tray } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
async function createWindow() {
// Create the browser window.
const win = new BrowserWindow({
width: 432,
height: 331,
alwaysOnTop: true,//窗口一直保持在其他窗口前面
modal: true,
frame: false,
darkTheme: true,
resizable: false,//用户不可以调整窗口
center: true, // 窗口居中
transparent: false,//窗口透明
show: false,// 显示窗口将没有视觉闪烁(配合下面的ready-to-show事件)
hasShadow: true,//窗口是否有阴影
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
// nodeIntegration: process.env.ELECTRON_NODE_INTEGRATION,
// contextIsolation: !process.env.ELECTRON_NODE_INTEGRATION,
devTools: true,//客户端可以打开开发者工具(在客户端打开快捷键:ctrl+shift+i)
nodeIntegration: true,
enableRemoteModule: true, // 使用remote模块(electron12版本就开始废除了,需要我们自己安装remote模块)
contextIsolation: false,
//解决axios请求跨域问题(不推荐,不安全,但简单好用)
webSecurity: false,
},
})
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
await win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
if (!process.env.IS_TEST) win.webContents.openDevTools()
} else {
createProtocol('app')
// Load the index.html when not in development
win.loadURL('app://./index.html')
}
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.once('ready-to-show', () => {
win.show();
})
app.on('ready', async () => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installExtension(VUEJS_DEVTOOLS)
} catch (e) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
5、安装remote模块
因为electron12版本开始就废除了remote模块,我们需要自己安装
npm install @electron/remote --save
//or
cnpm install @electron/remote --save
能在客户端实现 最大化、最小化、关闭功能
6、代码
1、Login.vue页面(登录页面)
里面的最小化图标、关闭图标、最大化图标 请自己去 iconfont-阿里巴巴矢量图标库 下载
<template>
<div class="login" style="overflow:hidden;">
<!-- 顶部 -->
<header class="header">
<!-- 最小化按钮 -->
<span @click="minwin" class="iconfont icon-24gl-minimization"></span>
<!-- 关闭按钮 -->
<span @click="close" class="iconfont icon-guanbi"></span>
</header>
<main>
<!-- 顶部背景图 -->
<div class="bg">
<img style="" src="../../assets/images/login.gif" />
</div>
<!-- 登录表单组件 -->
<div class="body">
<Login_form></Login_form>
</div>
</main>
<footer class="footer">
<div class="zczh" @click="zc">注册账号</div>
<div>
<span title="二维码登录" class="iconfont icon-erweima"></span>
</div>
</footer>
</div>
</template>
<script>
import "@/assets/css/login.css";
import "@/assets/fonts/gb/iconfont.css";
import "@/assets/fonts/zxh/iconfont.css";
import "@/assets/fonts/ewm/iconfont.css";
import Login_form from "@/components/Login_form.vue";
//网页测试要关闭,打包成软件要启动
const { remote } = window.require("electron");
export default {
name: "login",
data() {
return {};
},
components: {
Login_form,
},
methods: {
close() {
// 只是关闭窗口,软件不退出
// remote.getCurrentWindow().hide()
// 关闭窗口,退出软件
remote.getCurrentWindow().close();
},
// 最小化
minwin() {
// 最小化,在任务栏显示
remote.getCurrentWindow().minimize()
// 隐藏窗口,任务栏也隐藏
// remote.getCurrentWindow().hide();
},
zc() {
this.$router.push("/reg");
},
},
mounted() {
//显示窗口
remote.getCurrentWindow().show();
},
};
</script>
<style lang="scss" scpoed>
.login {
max-width: 900px;
overflow: hidden;
min-width: 430px;
.bg {
img {
width: 430px;
height: 124px;
object-fit: cover;
}
}
}
2、login.css
/**
取消全部的外边距和内边距
*/
* {
padding: 0;
margin: 0;
}
/*设置窗口的样式*/
.login {
cursor: default;
/*设置手型*/
/* border: 1px solid red; */
/*加一个边框 调试样式 最后要删除或者更改**/
width: 430px;
/*设置宽度 必须要和主进程中设置的一样 不能大于主进程中设置的宽度 否则会出现滚动条*/
height: 329px;
/*设置高度 必须要和主进程中设置的一样 不能大于主进程中设置的高度 否则会出现滚动条*/
position: relative;
/*设置为相对定位*/
/* border-radius: 4px; */
/*设置圆角*/
border: 1px solid #464545;
/* 拖 */
-webkit-app-region: drag;
/* 禁止滚动条 */
overflow: hidden;
}
/**
header的样式 header中只会有一个关闭按钮 处于右上角
*/
.login header.header {
position: absolute;
/*设置绝对定位 因为背景在他下面*/
height: 30px;
/*设置高度*/
/* border-radius: 20px 20px 0 0; */
/*给header的左上角 右上角设置圆角 不然会出现很尴尬的页面*/
width: 428px;
/* 因为设置了绝对定位 设置宽度*/
/* background: rgba(255, 255, 255, 0.2); */
/*暂时设置的 后面要删除或者更改*/
text-align: right;
}
/**
背景
*/
.login main .bg {
height: 124px;
/*设置高度*/
width: 430px;
/*设置宽度 也可以不用设置 因为这个元素没有设置绝对定位 所以默认就是100%*/
/* border-radius: 4px 4px 0 0; */
/*给左上角 右上角设置圆角 不然会出现很尴尬的页面 这里和header重合在一起了*/
/* background: url("@/assets/images/login.gif") 10px; */
background-size: 100%;
}
/**
放置表单的元素
*/
.login main .body {
width: 428px;
/*设置宽度 也可以不用设置 因为这个元素没有设置绝对定位 所以默认就是100%*/
height: 200px;
/*设置高度 这里的高度是 主窗口(326) - footer(30) - 背景(124) 因为header设置了绝对定位 所以不用关 */
/* background: green; */
/*暂时设置的 后面要删除或者更改*/
}
.login footer.footer {
position: absolute;
/* 设置绝对定位 要让他处于窗口的最底部*/
height: 30px;
/*设置高度 */
/* background: red; */
/*暂时设置的 后面要删除或者更改*/
bottom: 0;
/*让footer处于底部*/
width: 397.99px;
/* 因为设置了绝对定位 设置宽度*/
/* border-radius: 0 0 5px 5px; */
background-color: #FFFFFF;
}
.login header.header span{
display: inline-block;
height: 30px;
width:30px;
text-align: center;
line-height: 30px;
color:#E4393c;
cursor: pointer;
}
.login header.header span:hover{
background-color: rgba(255,255,255,0.6);
cursor: pointer;
}
.zczh{
cursor: pointer;
color: #7c7a7a;
user-select: none;
-webkit-app-region: no-drag;
}
.zczh:hover{
cursor: pointer;
color: black;
user-select: none;
-webkit-app-region: no-drag;
}
3、Login_form.vue组件
里面的resetMessage信息提示是我重新封装的element组件(我就不放上去了)
里面的最小化图标、关闭图标、最大化图标 请自己去 iconfont-阿里巴巴矢量图标库 下载
<template>
<div class="login_form">
<form>
<div class="form_item">
<i class="iconfont icon-zhanghao"></i
><input
@focus="i1_get"
@blur="i1_lose"
@input="input1($event)"
id="text"
v-model="email"
type="email"
:placeholder="placeholder1"
@keyup.enter="login"
/>
</div>
<div class="form_item">
<i class="iconfont icon-mima"></i
><input
@focus="i2_get"
@blur="i2_lose"
id="text2"
v-model="password"
type="password"
:placeholder="placeholder2"
@keyup.enter="login"
/>
</div>
<div class="login_options">
<label
><div class="option_item">
<input
:disabled="jzmm"
v-model="zddl"
type="checkbox"
autocomplete="off"
/>
</div>
<i class="text">记住账号</i></label
>
<label
><div class="option_item">
<input
@click="jzmm_mm"
v-model="jzmm"
type="checkbox"
autocomplete="off"
/>
</div>
<i class="text">记住密码</i></label
>
<i class="text wjmm">忘记密码</i>
</div>
</form>
<div class="buttons">
<button @click="login">登录</button>
</div>
</div>
</template>
<script>
import "@/assets/css/login_form.css";
import "@/assets/fonts/xlk/iconfont.css";
import "@/assets/fonts/slk/iconfont.css";
import "@/assets/fonts/zh/iconfont.css";
import "@/assets/fonts/mm/iconfont.css";
//网页测试要关闭,打包成软件要启动
const { remote } = window.require("electron");
export default {
name: "Login_form",
data() {
return {
email: "",
password: "",
placeholder1: "邮箱",
placeholder2: "密码",
zddl: false,
jzmm: false,
};
},
methods: {
i1_get() {
if (!this.email) {
this.placeholder1 = "";
}
},
i2_get() {
if (!this.password) {
this.placeholder2 = "";
}
},
i1_lose() {
if (!this.email) {
this.placeholder1 = "邮箱";
}
},
i2_lose() {
if (!this.password) {
this.placeholder2 = "密码";
}
},
input1(e) {
if (e.target.value == "") {
// console.log(val);
this.password = "";
this.placeholder2 = "密码";
}
},
login() {
var eamil_gz =
/^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/;
switch (true) {
case this.email == "":
this.waring("邮箱不能为空!");
break;
case !eamil_gz.test(this.email):
this.error("邮箱格式错误");
break;
case this.password == "":
this.waring("密码不能为空!");
break;
default:
let data = {
log_email: this.email,
log_password: this.password,
};
this.$axios
.post("/login/login", data)
// 在后台查询信息 返回res结果
.then((res) => {
// console.log(res.data);
switch (true) {
case res.data.code == 200:
//登录成功
this.success(res.data.msg);
//本地存储
localStorage.setItem("email", this.email);
localStorage.setItem("token", res.data.token);
if (this.zddl == true) {
localStorage.setItem("zddl", true);
} else {
localStorage.removeItem("zddl");
localStorage.removeItem("email");
}
if (this.jzmm == true) {
localStorage.setItem("zddl", true);
localStorage.setItem("jzmm", true);
localStorage.setItem("mm", this.password);
} else {
localStorage.removeItem("jzmm");
localStorage.removeItem("mm");
}
this.$router.push("/chat_index");
//登录成功跳转会有点闪屏,先隐藏,到另外一个路由再显示
remote.getCurrentWindow().hide();
//登录之后设置窗口在电脑桌面显示位置
remote.getCurrentWindow().setPosition(386, 192);
break;
case res.data.code == 404:
//邮箱不存在
this.error(res.data.msg);
break;
case res.data.code == 300:
//密码错误
this.error(res.data.msg);
break;
case res.data.code == 400:
//用户异常
this.error(res.data.msg);
break;
default:
//未知错误
this.error(res.data.msg);
break;
}
})
.catch((error) => {
//报错
});
break;
}
},
//记住密码
jzmm_mm() {
if (this.zddl == false) {
this.zddl = true;
}
this.jzmm = true;
},
// 警告提示
waring(title) {
this.$resetMessage({
message: `${title}`,
type: "warning",
});
},
// 成功提示
success(title) {
this.$resetMessage({
message: `${title}`,
type: "success",
});
},
// 错误提示
error(title) {
this.$resetMessage.error(`${title}`);
},
},
mounted() {
// 读取本地存储账号信息
if (localStorage.getItem("zddl")) {
this.zddl = true;
this.email = localStorage.getItem("email");
}
if (localStorage.getItem("jzmm")) {
this.jzmm = true;
this.password = localStorage.getItem("mm");
}
if (localStorage.getItem("email")) {
this.email = localStorage.getItem("email");
}
if (!this.email) {
document.getElementById("text").focus();
} else {
document.getElementById("text2").focus();
}
},
};
</script>
4、login_form.css
.login_form{
-webkit-app-region: drag;
background-color: #FFFFFF;
}
.login_form form {
padding: 10px 90px 0 90px;
}
.form_item {
height: 40px;
position: relative;
}
.form_item div {
float: right;
margin-right: 5px;
}
.form_item i.iconfont {
position: absolute;
top: 5px;
}
.form_item input {
outline: none;
border: none;
padding-left: 30px;
font-size: 16px;
width: 230px;
height: 30px;
border-bottom: 1px solid #CCC;
user-select: none;
-webkit-app-region: no-drag;
}
.buttons {
text-align: center;
}
.buttons button {
background-color: #0786b4;
border: none;
width: 250px;
color: #FFFFFF;
height: 35px;
cursor: pointer;
font-size: 14px;
border-radius: 4px;
outline: none;
-webkit-app-region: no-drag;
user-select: none;
}
.buttons button:hover {
background-color: #0aaae4;
border: none;
width: 250px;
color: #FFFFFF;
height: 35px;
cursor: pointer;
font-size: 14px;
border-radius: 4px;
outline: none;
-webkit-app-region: no-drag;
user-select: none;
}
.checkbox {
display: flex;
justify-content: center;
align-items: center;
}
.footer {
display: flex;
justify-content: space-between;
padding: 5px 15px 5px 15px;
align-items: center;
}
.login_options {
margin-bottom: 10px;
margin-top: 5px;
}
.login_options .option_item {
display: inline-block;
align-items: center;
width: 13px;
height: 13px;
margin-right: 5px;
position: relative;
cursor: pointer;
top: 2px;
-webkit-app-region: no-drag;
}
.login_options .option_item input {
/* font-size: 13px; */
-webkit-app-region: no-drag;
}
.login_options i.text {
display: inline-block;
margin-right: 14px;
font-size: 13px;
font-style: normal;
user-select: none;
-webkit-app-region: no-drag;
}
.login_options .option_item span.checked {
position: absolute;
top: -4px;
right: -3px;
font-weight: bold;
display: inline-block;
width: 20px;
height: 20px;
cursor: pointer;
}
.option_item span.checked img {
width: 100%;
height: 100%;
}
input[type="checkbox"]+span {
opacity: 0;
}
input[type="checkbox"]:checked+span {
opacity: 1;
}
.wjmm {
cursor: pointer;
color: rgb(139, 134, 134);
}
.wjmm:hover {
cursor: pointer;
color: rgb(19, 18, 18);
}
5、package.json文件
{
"name": "qq_test",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"main": "background.js",
"dependencies": {
"axios": "^0.27.2",
"core-js": "^3.8.3",
"element-ui": "^2.15.9",
"express": "^4.18.1",
"qs": "^6.11.0",
"socket.io": "^4.5.1",
"socket.io-client": "^3.1.0",
"vscode-material-icon-theme-js": "^1.0.7",
"vue": "^2.6.14",
"vue-router": "^3.5.1",
"vue-socket.io": "^3.0.10",
"vuex": "^3.6.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"electron": "^12.0.0",
"electron-devtools-installer": "^3.1.0",
"sass": "^1.32.7",
"sass-loader": "^12.0.0",
"vue-cli-plugin-electron-builder": "~2.1.1",
"vue-template-compiler": "^2.6.14"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
6、main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import axios from "axios"; //引入axios
Vue.prototype.$axios = axios; //axios跟很多第三方模块不同的一点是它不能直接使用use方法,而是用这种方法
//配合文章第4步解释 本地网站
axios.defaults.baseURL = 'http://www.electron.com/index.php/api';//开发环境
//引入element组件
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
//重写提示框只提示一次
import resetMessage from '@/assets/js/resetMessage'
Vue.prototype.$resetMessage = resetMessage
Vue.use(
new VueSocketIO({
debug: false,
// 宝塔 IP:端口号 (生产环境)
connection: SocketIO('http://127.0.0.1:3000', {//开发环境
autoConnect: false // 取消自动连接
}),
extraHeaders: { 'Access-Control-Allow-Origin': '*' }
})
)
//客户端禁止按鼠标返回键返回上一个路由
window.addEventListener('popstate', function() {
history.pushState(null, null, document.URL)
})
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
7、效果图
本地测试
npm run electron:serve