前言
Electron是由Github開發,用HTML,CSS和JavaScript來構建跨平臺桌面應用程式的一個開源庫。 Electron透過將Chromium和Node.js合併到同一個執行時環境中,並將其打包為Mac,Windows和Linux系統下的應用來實現這一目的。
Electron於2013年作為構建Github上可程式設計的文字編輯器Atom的框架而被開發出來。這兩個專案在2014春季開源。
目前它已成為開源開發者、初創企業和老牌公司常用的開發工具。 看看誰在使用Electron 。
繼續往下閱讀可以瞭解Electron的貢獻者們和已經發布的版本,或者直接閱讀快速開始指引來開始用Electron來構建應用。
(摘抄至electronjs.org)
一、初始化專案
執行,vue init simulatedgreg/electron-vue 專案名稱
vue init simulatedgreg/electron-vue admin
這裡的專案名稱是“admin”
如果沒有安裝vue腳手架,請檢視《spring boot + vue + element-ui全棧開發入門——windows開發環境》
一路回車
然後執行npm install來安裝依賴,執行方式和之前一樣。
如果遇到run dev或者run build的時候出錯,可能是因為國內的網路下載“electron-v1.8.3-win32-x64.zip”出錯,這時,你需要設定npm的代理:
npm config set proxy http://伺服器IP或域名:埠號 npm config set https-proxy http://伺服器IP或域名:埠號
如果需要使用者名稱密碼:
npm config set proxy http://使用者名稱:密碼@伺服器IP或域名:埠號
npm config set https-proxy http://使用者名稱:密碼@伺服器IP或域名:埠號
設定回原庫
npm config set registry http://registry.npmjs.org
關閉代理
npm config delete proxy npm config delete https-prox
也可以使用yarn。
npm install -g yarn
安裝依賴、開發模式執行和程式設計的命令分別是:
yarn install yarn run dev yarn run build
專案構建完畢後,結構如下圖所示:
和之前專案區別是,main是用於桌面程式的程式碼,render是用於渲染的程式碼。我們只需要在render資料夾裡寫程式碼就可以。
開發模式執行:
npm run dev
二、程式碼編寫
參照《spring boot + vue + element-ui全棧開發入門——整合element-ui》安裝所需的依賴
cnpm install --save element-ui cnpm install --save-dev node-sass cnpm install --save-dev sass-loader
cnpm install --save font-awesome
參照《spring boot + vue + element-ui全棧開發入門——前端列表頁面開發》的程式碼如下:
入口檔案:
import Vue from 'vue' import axios from 'axios' import App from './App' import router from './router' import store from './store' if (!process.env.IS_WEB) Vue.use(require('vue-electron')) Vue.http = Vue.prototype.$http = axios axios.defaults.baseURL = 'http://localhost:18080' Vue.config.productionTip = false import 'font-awesome/css/font-awesome.min.css' import ElementUI from 'element-ui' //原始風格 // import 'element-ui/lib/theme-chalk/index.css' //自定義風格 import './assets/theme/element-#09345f/index.css' Vue.use(ElementUI) /* eslint-disable no-new */ new Vue({ components: { App }, router, store, template: '<App/>' }).$mount('#app')
其中 axios.defaults.baseURL = 'http://localhost:18080' 是設定後端專案URL,而這可以根據具體情況寫到配置檔案中,開發環境呼叫開發環境的配置,生產環境呼叫生產環境配置。
路由檔案:
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) import Main from '@/pages/Main' import Dashboard from '@/pages/Dashboard' import Member from '@/pages/Member' // let routes = [ // { // path: '/', // name: 'landing-page', // component: require('@/components/LandingPage').default // }, // { // path: '*', // redirect: '/' // } // ] let routes = [{ path: '/', component: Main, hidden: true, children: [{ path: '/', component: Dashboard, name: '首頁' }] }] routes.push({ path: '/member', name: '會員管理', component: Main, iconCls: 'fa fa-user-circle-o', children: [{ path: '/member/data', component: Member, name: '會員資訊管理' }] }) const router = new Router({ routes: routes }) export default router
主頁面:
<template> <section> <el-container class="container"> <!--左邊--> <el-aside :width="collapsed? '75px' : '280px' "> <el-container> <el-header> <span class="menu-button" v-if="collapsed" @click.prevent="collapsed=!collapsed"> <i class="fa fa-align-justify"></i> </span> <span v-else class="system-name">{{systemName}}</span> </el-header> <el-main> <el-menu :default-active="$route.path" :collapse="collapsed" :style="{'height':menuHeight}"> <template v-for="(item,index) in menus"> <el-submenu :index="index+''" v-if="!item.leaf"> <template slot="title"><i :class="item.iconCls"></i><span v-if="!collapsed">{{item.name}}</span></template> <el-menu-item v-for="child in item.children" :index="child.path" :key="child.path" @click="$router.push(child.path)">{{child.name}}</el-menu-item> </el-submenu> <el-menu-item v-if="item.leaf&&item.children.length>0" :index="item.children[0].path"><i :class="item.iconCls"></i>{{item.children[0].name}}</el-menu-item> </template> </el-menu> </el-main> </el-container> </el-aside> <!--內容--> <el-container> <!--頁首--> <el-header class="header"> <el-row> <el-col :span="18" class="header-title"> <span v-if="collapsed" class="system-name">{{systemName}}</span> <span v-else class="menu-button" @click.prevent="collapsed=!collapsed"> <i class="fa fa-align-justify"></i> </span> </el-col> <el-col :span="6"><span class="el-dropdown-link userinfo-inner">你好:{{userName}}</span></el-col> </el-row> </el-header> <!--中間--> <el-main class="main"> <transition name="fade" mode="out-in"> <router-view></router-view> </transition> </el-main> </el-container> </el-container> </section> </template> <script> let data = () => { return { collapsed: false, systemName: '後臺管理', userName: '系統管理員', menuHeight: '100%', menus: [] } } let initMenu = function() { for (let i in this.$router.options.routes) { let root = this.$router.options.routes[i] if (root.hidden) continue let children = [] for (let j in root.children) { let item = root.children[j] if (item.hidden) continue children.push(item) } if (children.length < 1) continue this.menus.push(root) root.children = children } } let initHeight = function() { this.menuHeight = (document.documentElement.clientHeight - 60) + 'px' } export default { data: data, methods: { initMenu, //初始化高度 initHeight }, mounted: function() { this.initHeight() window.addEventListener('resize', this.initHeight) this.initMenu() } } </script> <style scoped="scoped" lang="scss"> $width: 100%; $height: 100%; $background-color: #09345f; $header-color: #fff; $header-height: 60px; .container { position: absolute; top: 0; bottom: 0; width: 100%; .el-aside { .el-header { line-height: $header-height; background-color: $background-color; color: $header-color; text-align: center; } .el-container { height: $height; .el-main { padding: 0; } } } .main { width: $width; height: $height; } .menu-button { width: 14px; cursor: pointer; } .userinfo-inner { cursor: pointer; } .el-menu { height: $height; } .header { background-color: $background-color; color: $header-color; text-align: center; line-height: $header-height; padding: 0; .header-title { text-align: left; span { padding: 0 20px; } } } .system-name { font-size: large; font-weight: bold; } } </style>
會員資料列表頁面:
<template> <section> <!--工具條--> <el-col :span="24" class="toolbar" style="padding-bottom: 0px;"> <el-form :inline="true" :model="filters"> <el-form-item> <el-input v-model="filters.query" placeholder="姓名/手機號等條件" /> </el-form-item> <el-form-item> <el-button type="primary" v-on:click="handleQuery" icon="el-icon-search">查詢</el-button> </el-form-item> <el-form-item> <el-button type="primary" v-on:click="handleAdd" icon="el-icon-plus">新增</el-button> </el-form-item> </el-form> </el-col> <el-table :data="rows" style="width: 100%;overflow: auto;" :height="clientHeight" stripe border highlight-current-row v-loading="pageLoading"> <el-table-column label="註冊日期" width="180"> <template slot-scope="scope"> <i class="el-icon-time"></i> <span style="margin-left: 10px">{{ scope.row.date }}</span> </template> </el-table-column> <el-table-column label="姓名" width="180" :show-overflow-tooltip="true"> <template slot-scope="scope"> <el-popover trigger="hover" placement="top"> <p>姓名: {{ scope.row.name }}</p> <p>住址: {{ scope.row.address }}</p> <div slot="reference" class="name-wrapper"> <el-tag size="medium">{{ scope.row.name }}</el-tag> </div> </el-popover> </template> </el-table-column> <el-table-column prop="sex" label="性別" width="100" align="center" :show-overflow-tooltip="true"> <template slot-scope="scope"> {{scope.row.sex===1?'男':'女'}} </template> </el-table-column> <el-table-column label="操作"> <template slot-scope="scope"> <el-button size="mini" type="primary" @click="handleEdit(scope.$index, scope.row)"><i class="el-icon-edit"></i>編輯</el-button> <el-button size="mini" type="danger" @click="handleDelete(scope.$index, scope.row)"><i class="el-icon-delete"></i>刪除</el-button> </template> </el-table-column> </el-table> <!--底部--> <el-col :span="24" class="toolbar"> <el-pagination layout="prev, pager, next" @current-change="handleCurrentChange" :page-size="20" :total="total" style="float:right;"> </el-pagination> </el-col> <!--對話方塊--> <el-dialog :title="form && form.id ? '編輯' : '新增' " :visible.sync="formVisible" :close-on-click-modal="false"> <el-form :model="form" label-width="100px" :rules="rules" ref="form"> <el-form-item label="姓名" prop="name"> <el-input v-model="form.name" /> </el-form-item> <el-form-item label="性別" prop="sex"> <el-radio-group v-model="form.sex"> <el-radio :label="1">男</el-radio> <el-radio :label="2">女</el-radio> </el-radio-group> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click.native="formVisible = false">取消</el-button> <el-button type="primary" @click.native="handleSubmit" :loading="formLoading">提交</el-button> </div> </el-dialog> </section> </template> <script> const rules = { name: [{ required: true, message: '請輸入姓名', trigger: 'blur' }], sex: [{ required: true, message: '請選擇性別', trigger: 'change' }] } let data = () => { return { //頁碼 page: 1, //每頁數量 size: 20, //總數 total: 0, //查詢條件 filters: {}, //頁面資料 rows: [], //頁面載入狀態 pageLoading: false, //列表高度 clientHeight: '100%', //表單資料 form: {}, //驗證規則 rules: rules, //對話方塊隱藏狀態 formVisible: false, //表單提交狀態 formLoading: false } } let handleAdd = function() { this.form = {} this.form.sex = 1 this.formVisible = true } let handleEdit = function(index, row) { this.form = Object.assign({}, row) this.formVisible = true } let handleDelete = function(index, row) { if (this.pageLoading) return this.$confirm('此操作將永久刪除該資料, 是否繼續?', '提示', { confirmButtonText: '確定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.pageLoading = true this.$http.get('/member/remove/' + row.id).then(res => { this.pageLoading = false if (!res.data.success) { this.$message({ type: 'error', message: res.data.message }) return } this.$message({ type: 'success', message: '刪除成功!' }) this.page = 1 this.getRows() }).catch(e => this.pageLoading = false) }).catch(e => {}) } let getRows = function() { if (this.pageLoading) return this.pageLoading = true let params = { page: this.page, size: this.size, query: this.filters.query } //呼叫post請求 this.$http.post('/member/loadPage', params).then(res => { this.pageLoading = false if (!res.data || !res.data.rows) return //總數賦值 this.total = res.data.total this.page++; //頁面元素賦值 this.rows = res.data.rows }).catch(e => this.pageLoading = false) } let handleSubmit = function() { if (this.formLoading) return this.$refs.form.validate(valid => { if (!valid) return this.formLoading = true //呼叫http協議 this.$http.post('/member/save', this.form).then(res => { this.formLoading = false if (!res.data.success) { this.$message({ showClose: true, message: res.data.message, type: 'error' }); return } this.$message({ type: 'success', message: '儲存成功!' }) //重新載入資料 this.page = 1 this.getRows() this.formVisible = false }).catch(e => this.formLoading = false) }) } let handleQuery = function() { this.page = 1 this.getRows() } let handleCurrentChange = function(val) { this.page = val this.getRows() } let initHeight = function() { this.clientHeight = (document.documentElement.clientHeight - 258) + 'px' } export default { data: data, methods: { //查詢 handleQuery, //新增 handleAdd, //修改 handleEdit, //刪除 handleDelete, //頁數改變 handleCurrentChange, //獲取分頁 getRows, //初始化高度 initHeight, //提交資料 handleSubmit }, mounted: function() { window.addEventListener('resize', this.initHeight) this.initHeight() this.getRows() } } </script> <style scoped> </style>
結構如下圖所示:
還有,在執行之前,我們需求修改src/main/index.js的配置:
function createWindow() { /** * Initial window options */ mainWindow = new BrowserWindow({ height: 563, useContentSize: true, width: 1000, webPreferences: { webSecurity: false } })
其目的是為了實現js跨域。
執行之前專案的後端專案《spring boot + vue + element-ui全棧開發入門——spring boot後端開發》:
mvn package java -jar target/demo.jar
執行專案,效果如下:
二、生成安裝包
npm run build
如提示缺少vue組建,是因為registry的問題,因為國內taobao映象沒有Electron的依賴環境。所以需要設定回預設的 registry,並使用設定proxy的方式下載依賴環境。
如果提示“icon source "build/icons/icon.ico" not found”
就把“icons”加到build目錄下,下載icons請點選連結,根據具體情況修改icons。
生成好後,出現“admin Setup 0.0.0.exe”的檔案,即安裝程式。
我運用這個安裝程式後,開啟剛剛開發好的程式,效果如圖所示:
發現,雖然只用到了一些前端技術,但已經能夠開發出桌面應用了。小時候,老師說:“學好數理化,走遍天下都不怕”。而現在是:“學會了node,任何平臺的前端都不怕”。
程式碼下載地址 : https://github.com/carter659/electron-vue-example.git
如果你覺得我的部落格對你有幫助,可以給我點兒打賞,左側微信,右側支付寶。
有可能就是你的一點打賞會讓我的部落格寫的更好:)