spring boot + vue + element-ui全棧開發入門——基於Electron桌面應用開發

冬子哥發表於2018-03-08

 前言


 

  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')
main.js

其中 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
router/index.js

 

 主頁面:

<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>
Main.vue

 

會員資料列表頁面:

<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>
Member.vue

 

結構如下圖所示:

 

 

還有,在執行之前,我們需求修改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


如果你覺得我的部落格對你有幫助,可以給我點兒打賞,左側微信,右側支付寶。

有可能就是你的一點打賞會讓我的部落格寫的更好:)

作者:劉冬.NET 部落格地址:http://www.cnblogs.com/GoodHelper/ 歡迎轉載,但須保留版權

相關文章