• spring boot + vue + element-ui全栈开发入门——基于Electron桌面应用开发


     前言


      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">
    $ 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=" 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,
         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/ 欢迎转载,但须保留版权
  • 相关阅读:
    如何安装mysql
    07 登录接口开发
    06 跨域问题
    05 实体校验
    04 异常处理
    03 整合shiro+jwt 会话共享
    02 统一结果封装
    01 新建SpringBoot项目 整合Mybatis Plus(Spring Boot 前后端分离)
    结合Scikit-learn介绍几种常用的特征选择方法
    Set_ML
  • 原文地址:https://www.cnblogs.com/GoodHelper/p/8527523.html
Copyright © 2020-2023  润新知