美文网首页vueVue项目vue
《基于Vue2.0的Web移动应用开发》- 基础

《基于Vue2.0的Web移动应用开发》- 基础

作者: 健叔_31a3 | 来源:发表于2019-04-18 17:07 被阅读185次

    目录

    • 一、前言
    • 二、环境配置
      • 2.1 Nodejs下载与安装
      • 2.2 Visual Studio Code安装
      • 2.3 npm淘宝镜像配置
    • 三、代码运行
    • 四、代码讲解
      • 4.1 config配置
        • 4.1.1 dev与prod
        • 4.1.2 跨域代理
      • 4.2 代码结构-src
        • 4.2.1 程序入口-main.js
        • 4.2.2 路由配置-router
        • 4.2.3 页面布局-view
        • 4.2.4 组件-component
        • 4.2.5 服务-service
        • 4.2.6 状态管理-store
        • 4.2.7 模拟数据-mock
        • 4.2.8 其他
    • 五、业务开发

    • 六、项目部署

    • 七、总结


    一、前言

    本文主要描述开发一个基于vue2.0的移动应用所需的环境安装、配置、代码获取、调试、业务逻辑开发、与企业微信集成等等整个过程;通读全文后可以完成一个移动应用从无到有的搭建、开发和部署。主要用到的技术栈如下:

    • Nodejs
    • vue2.0
    • Vue-Router
    • webpack
    • Less
    • vuex
    • vux
    • axios
    • 阿里iconfont
    • 企业微信SDK

    文中用到了开源社区基于微信团队weUI的Vux UI组件库,在此表示对Vux作者的万分感谢。


    二、环境配置

    2.1 Nodejs下载与安装地址
    下载并安装成功后,在cmd下执行如下指令,确认是否安装成功:

    C:\Users\xxx> node -v
    v10.15.0
    

    输出版本号则表明安装成功了。

    本文仅举例了Windows平台的安装过程,Linux/Unix等其他操作系统请从查找其他网络资源参考。

    2.2 Visual Studio Code安装地址
    vscode的安装就不赘述了。安装完成后,可以一并安装vscode的下列插件:
      2.1.Vetur
      2.2.Chinese (Simplified) Language Pack for Visual Studio Code
      2.3.CSS Formatter
    等等实用插件。

    2.3 npm淘宝镜像配置:
    在cmd中执行代码:npm install -g cnpm --registry=https://registry.npm.taobao.org
    稍等几分钟,完成npm淘宝镜像的安装。

    如在日常的开发工作中,发现npm或cnpm相关指令执行非常慢,甚至获取版本号都非常慢直至卡死,可以执行如下语句:npm config set registry "http://registry.npm.taobao.org/"


    三、代码运行

    • 获取代码地址【略】
      获取到代码后,用Visual Studio Code编辑器打开代码的根目录。具体如下:启动Visual Studio Code --【文件】--【打开文件夹】选择项目的根目录。

    • cnpm install
      在项目根目录下执行cmd指令:cnpm install,以安装项目的依赖。

    • 配置本地ip:
        如果需要在手机中调试,直接使用localhost是不可取的,需要配置ip,然后手机连接到与pc同网段下的wifi进行访问即可。

      image.png
    • npm run dev
      在项目根目录下执行cmd指令:npm run dev,编译完成后如下:

      image.png

    同时按下Ctrl+鼠标点击图中http://172.xx.xx.xxx,浏览器则打开如下图的页面:

    image.png

    开发小技巧:在Visual Studio Code下进行开发,无需另外打开一个cmd窗口进行指令执行,可在Visual Studio Code通过点击【查看】-【终端】打开Visual Studio Code自带的cmd入口进行指令执行和监控代码编译进度等。详情参考Visual Studio Code的使用说明。


    四、代码讲解

    image.png

    其中:

    • build:webpack相关打包应用配置
    • config:应用开发环境下的dev和prod相关配置
    • src:源码
    • static:静态资源,如多语言配置
    • test:单元测试相关

    4.1 config-配置

    • 4.1.1 dev与prod
      这是环境变量定义,即,在dev和prod两个文件定义一个变量,如,dev下,a=0,而prod下,a=1,那么,在执行npm run dev命令下的上下文中,a=0,而在采用npm run build指令下,a=1。
      如dev文件:\config\dev.env.js
    'use strict'
    const merge = require('webpack-merge')
    const prodEnv = require('./prod.env')
    
    module.exports = merge(prodEnv, {
      NODE_ENV: '"development"',
      API_ROOT:'"http://172.0.0.1:8443/yyy/rest/"',
      LOGMODE:true,
    })
    
    

    prod文件:\config\prod.env.js

    'use strict'
    module.exports = {
      NODE_ENV: '"production"',
      LOGMODE: false,
      API_ROOT: '"https://xxx.xx.xx.xxx:8443/yyy/rest/"',
    }
    

    如此,在其他上下文中,可以通过process.env.LOGMODE来访问当前环境变量下的值了。即、在npm run devprocess.env.LOGMODE=true;而在npm run buildprocess.env.LOGMODE=false;

    使用实例:

    //日志记录,在调试模式下才打印日期,而在build下不会打印日志。
        log(msg) {
            if (process.env.LOGMODE) {
                console.log("Gac Logger:" + msg);
            }
        },
    
    • 4.1.2 跨域代理
      大部分前后端分离的项目中,前端访问的后台接口,都不在前端的同一个域名/ip/端口之下,这样一来是必须解决跨域问题的。在\config\index.js文件下的module.exports下的dev节点下,增加类似如下代码进行跨域代理:
    proxyTable: {
          //配置代理,解决跨域问题 by LeoFeng 2018-9-13 09:27:15
          '/api': {
            target: 'http://172.31.254.125:8443/gacrdp/rest/', 
            changeOrigin: true, 
            pathRewrite: {
                '^/api': ''   //路径重写
            }
        }
    }
    ...
    

    如此一来,在dev环境下访问相关后台的api时,可以用'/api'前缀来替代出现跨域问题的后台接口前缀,nodejs将为应用处理中转。

    如当前需要调用后台的getList的Rest API接口,值可以这样:

    axios.get('/api/getList')
      .then(function (response) {
        console.log(response);
      })
      .catch(function (error) {
        console.log(error);
      });
    

    nodejs会把请求的/api/getList地址,中转到http://172.31.254.125:8443/gacrdp/rest/getList这个接口地址上,并最终完成数据请求。

    4.2 代码结构-src

    image.png
    • assets:资源如css,js,image
    • components:用户自定义组件
    • config:用户自定义配置
    • mock:模拟数据
    • router:路由配置
    • service:请求服务配置
    • store:状态管理
    • utils:通用工具
    • views:应用开发的页面
    • App.vue:启动页
    • main.js:程序主函数

    4.2.1 程序入口-main.js

    公共UI组件注册:

    import {
      AlertPlugin,
      AjaxPlugin, WechatPlugin, LoadingPlugin, ToastPlugin, ConfirmPlugin, Popup, PopupPicker, Popover, XButton, PopupHeader, Cell, Group, CellBox, Badge, XHeader, Search
      , XInput
    } from 'vux'
    Vue.component('popup', Popup)
    Vue.component('popover', Popover)
    Vue.component('x-button', XButton)
    Vue.component('popup-header', PopupHeader)
    Vue.component('cell', Cell)
    Vue.component('group', Group)
    Vue.component('cell-box', CellBox)
    Vue.component('badge', Badge)
    Vue.component('x-header', XHeader)
    Vue.component('search', Search)
    Vue.component('popup-picker', PopupPicker)
    Vue.component('x-input', XInput)
    

    状态管理与多语言引入:

    import store from './store/index'//状态管理(全局使用) LeoFeng 2018-10-8 15:09:39
    import LangEn from '../static/lang/en'
    import LangZhCHS from '../static/lang/zh-CNS'
    import LangZhCHT from '../static/lang/zh-CNT'
    
    //多语言
    const i18n = new VueI18n({
      locale: 'zh-CHS',
      messages: {
        'en': LangEn,
        'zh-CHS': LangZhCHS,
        'zh-CHT': LangZhCHT
      }
    })
    

    渲染挂载:

    new Vue({
      router,
      i18n,
      store,
      render: h => h(App)
    }).$mount('#app-box')
    

    4.2.2 路由配置-router

    本框架采用了Vue-Router进行页面导航,每一个需要展示的页面都需要在路由配置中申明。\src\router\index.js代码节选如下:

    import Vue from 'vue'
    import Router from 'vue-router'
    import GacDefault from '@/views/Default/Default'
    import DeviceMarket from '@/views/DeviceMarket/DeviceMarket'
    import DeviceDetail from '@/views/DeviceDetail/DeviceDetail'
    Vue.use(Router)
    
    export default new Router({
      routes: [
        {
          path: '/',
          name: 'default',
          meta: {
            title: '设备仓库'
          },
          component: GacDefault,
          children: [{
            path: '/',
            name: 'DeviceMarket',
            meta: {
              title: '设备仓库'
            },
            component: DeviceMarket,
            children: []
          }
          ]
        },
        {
          path: '/device-detail/:id',
          name: 'device-detail',//path跟name最好相同。
          meta: {
            title: '设备明细'
          },
          component: DeviceDetail,
          children: []
        }
      ]
    })
    
    

    使用方式:
    普通导航方式:<router-link to="/">default</router-link>
    编程式的导航[带参数]方式:this.$router.push({ name: "device-detail", params: { id } });

    更多导航方式和参数获取方式请参考Vue-Router官方文档。

    4.2.3 页面布局-views

    应用开发的页面,集中存放在\src\views\下:

    image.png
    如业务上需要新增一个页面并展示出来,如名称为student的页面,步骤如下:
    • views文件夹下新增一个Student子文件夹,并在子文件夹内新建一个页面的Student.vue文件;
    • student.vue内进行页面布局和相关逻辑开发;
    • 开发完成后,在\src\router\index.js的路由配置文件下进行引用:
    import Student from '@/views/Student/Student'
    

    然后在路由根节点中申明【非嵌套路由场景】:

    {
            path: '/student',
            name: 'student',
            meta: {
              title: '学生记录'
            },
            component: Student,
            children: []
    }
    

    展示时只需添加如下导航标签即可:

    <router-link to="/student">学生记录</router-link>
    

    4.2.4 组件-components

    当应用的业务包含较多通用功能,且这些功能都相对独立时,我们就自然而然的会想到将这些通用的功能独立出来,写成一个通用的组件。

    Vue的组件无非包含以下几个常用的概念:

    • 属性定义:props
    • 事件上报:$emit('事件名','参数')
    • 插槽:<slot></slot>
    • 值绑定:v-model

    当涉及到父、子组件通信时,通常采用props实现父组件向子组件传递数据;而子组件则通过$emit()上报事件到父组件,父组件再通过@event-name来捕捉子组件上报的事件。

    属性-定义:代码节选自:\src\components\gacDropItem.vue

    ...
      props: {
        options: {
          type: [Object],//申明属性类型
          required: false//申明是否为必选属性
        },
        value: {
          default: ""//value的定义,是为了给当前组件实现v-model的功能
        }
      },
      methods: {
        onCheck: function(eqId) {
          ...
          this.$emit("input", param); //v-model必备!配合props中的value,组件的v-model功能就实现了。
          this.$emit("on-checked", param);//上报on-checked事件
        },
    ...
    

    属性-插槽
    例举:\src\components\gacScroller.vue

    <template>
      <div class="cScroller">
        <scroller ...[省略属性设置]>
          <slot></slot><!--定义插槽-->
        </scroller>
      </div>
    </template>
    

    属性-应用
    属性、事件和v-model的应用:

    <gacDropItem
            v-for="(item2,index2) in item.data"
            :key="item2.equipmentId"
            :options="item2"
            v-model="toApproveEqIds[parseInt(index1.toString()+index2.toString())]"
            @on-checked="onDropItemCheck"
          >
    </gacDropItem>
    

    插槽的应用:

    <gacScroller
              ref="gacScroller"
              :xHeight="'-162'"
              @on-load-more="loadMore"
              @on-refresh="refresh"
            >
              <div>
                <!--这里就是插槽的容器,放置要显示的内容-->
              </div>
    </gacScroller>
    

    注意!自定义组件的应用,首先需要完成组件的开发;其次在需要引用组件的页面引用组件:import gacDropItem from "./../../components/gacDropItem";;接着在页面中注册组件:components: { gacDropItem },;最后才参考上图进行组件的应用。

    4.2.5 服务

    服务,专注于应用的数据请求,主要包括:

    • API URL配置
    • API 封装
    • API 请求

    4.2.5.1 api url 配置: \src\service\api.js,详细如下:

    const api = {
      GetToken: '/api/tokens',
      GetUserInfo: '/api/meetingApi/getUserInfo',
    
      /************************业务系统api************************/
    ...
      //获取用户类型
      nvh_getUserType: '/nvh-api/getUserType',
      //获取设备分类
      nvh_getTypeList: '/nvh-api/typeList',
    ...
      /************************业务系统api************************/
    
    }
    export default api
    

    4.2.5.2 API封装:

    import Vue from 'vue'
    import api from '../api'
    import { USER_INFO } from '@/store/mutation-types'
    import { axios } from '@/utils/request'
    
    //获取类型列表
    export function getTypeList(deptCode = '') {
      return axios({
        url: api.nvh_getTypeList + "?deptCode=",
        method: 'GET',
        data: {}
      })
    }
    ...
    //确认下单
    export function doSubmitBorrow(params) {
      return axios({
        url: api.nvh_doSubmitBorrow ,
        method: 'POST',
        data: params
      })
    }
    ...
    
    

    4.2.5.3 API请求:

    • 在需要请求api接口的页面,引用服务:
    import { getEquipmentList } from "./../../service/modules/nvhDevice";
    
    • 在需要调用API接口的地方,写类似如下请求即可:
    ...
          getEquipmentList(this.equipmentParams).then(
            res => {
              //todo with res.data
            },
            err => {
              //todo with err
            }
          );
    ...
    

    4.2.6 状态管理

    无论是Vue还是React,状态管理都是一个非常重要的概念。本项目应用了Vuex的状态管理组件,需要更深入了解的请参考Vuex官方文档。以下主要讲解Vuex的应用,在项目中的代码结构如下:

    image.png
    • modules文件夹下,主要是将不同业务的状态管理分隔开来,便于管理。
    • index.js文件是全局的状态管理入口,它集合了modules文件夹下各子业务的状态管理实现,并对外暴露了一个Vuex.Store对象。

    状态管理主要包括:

    • State:可以理解为据图存储的数据对象;

    • Mutation:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation;

    • Action:Action 类似于 mutation,不同在于:

      • Action 提交的是 mutation,而不是直接变更状态。
      • Action 可以包含任意异步操作
    • Getter:可以理解为数据库中的视图view,是state的一种过滤后的展现。

    • Module:使得状态管理可以模块化,便于代码管理。

    4.2.6.1 状态定义

    例举:\src\store\modules\app.js

    import Vue from 'vue'
    import { ACCESS_TOKEN, USER_INFO } from '@/store/mutation-types'
    import { getToken, getUserInfo } from './../../service/modules/app'
    const app = {
        state: {
            token: '',//最终存储Token的状态
            userinfo :'',//用户信息的状态
            ...
        },
        mutations: {
            SET_ACCESS_TOKEN: (state, _token) => {
                state.token = _token
                Vue.ls.set(ACCESS_TOKEN, _token)
            },
            ...
        },
        actions: {
            GetAccessToken({ commit }, params) {
                return new Promise((resolve, reject) => {
                    console.log('innnnnnnnn Action.')
                    getToken(params).then(response => {
                        console.log(JSON.stringify(response))
                        if (response.length <= 128) {
                            Vue.ls.set(ACCESS_TOKEN, response, 7 * 24 * 60 * 60 * 1000)
                            commit('SET_ACCESS_TOKEN', response)
                            console.log("get Token success.")
                        } else {
                            console.log('Token 长度明显异常。')
                        }
                        resolve()
                    }).catch(error => {
                        reject(error)
                    })
                })
            },
            ...
        },
        getters: {
            gCoverList: state => { return state.coverList },
            gUserInfo: state => { return state.userinfo }
        }
    }
    
    export default app
    

    4.2.6.2 模块组合
    完成上述模块的定义后,在\src\store\index.js中进行集成。

    import Vue from 'vue'
    import Vuex from 'vuex'
    
    import app from './modules/app'
    import wechat from './modules/wechat'
    import carList from './modules/deviceCar'
    import nvhDevice from './modules/nvhDevice'
    Vue.use(Vuex)
    
    export default new Vuex.Store({
        modules: {
            app,
            wechat,
            carList,
            nvhDevice
        },
        state: {
    
        },
        mutations: {
    
        },
        actions: {
    
        },
        getters: {
    
        }
    })
    
    

    4.2.6.3 状态应用
    步骤如下:

    • 在需要应用指定状态管理的页面,引用如下代码(参考:\src\views\Car\Car.vue):
    import { mapGetters, mapActions } from "vuex";
    

    然后,向当前上下文的methods下注册如下方法:

    //具体应用-action
    ...mapActions(["RemoveFromCarList", "AddToCarList", "ClearCarList"]),
    
    

    接着向当前上下文中的computed注册如下计算属性:

    //具体应用-getters
    ...mapGetters(["carList", "gUserInfo"])
    

    此时,在当前页面的上下文下,即可直接调用方法和获取属性:

    //action的调用
    this.RemoveFromCarList(_id);
    
    //carList属性的使用
    this.carList
    

    另外,提交状态也可以通过直接提交(commit) Mutation来实现:

    this.$store.commit("mutation-name", 'params')
    

    getter和state也可以直接通过$store来访问:

    //直接访问getter
    this.$store.getters.[getter名];
    
    //直接访问state
    this.$store.state.[module-name].[state-name]
    

    注意:状态的变更响应机制常常会给我们带来很大的便捷,但是,并不是所有的数据都有必要存储在store中,否则会大大增加系统的开销,影响性能。另外,如果一个非常小的应用,也不一定非要使用Vuex这样的状态管理框架,反倒是可以通过简单的数据总线就可以实现类似的功能。

    4.2.7 模拟数据

    应用在调试过程中需要很多模拟数据,集中放在\src\mock下:

    image.png

    在需要模拟数据的页面,添加类似引用:

    import TabMenuitems from "./../../mock/app/tabMenus";
    

    便可在上下文中直接引用这些模拟数据,如:

    computed: {
        ...mapGetters(["carListCount"]),
        tabItems: () => {
          return TabMenuitems;
        }
      },
    

    4.2.8 其他

    • 皮肤重写
      本项目采用了基于weUI的Vux UI组件,这样要面临一个问题就是,当实际项目中需要修改指定的样式时,会比较麻烦。因此项目统一接入了主题重写入口,具体如下:
      首先,在\build\webpack.base.conf.js文件下的module.exports->plugins下新增一个插件less-theme,指定文件路径为:src/assets/theme/theme.less
    module.exports = vuxLoader.merge(webpackConfig, {
      plugins: [
        'vux-ui',
        'progress-bar',
        ...,
        { name: 'less-theme', path: 'src/assets/theme/theme.less' }/*提供重写Vux框架皮肤功能 add by LeoFeng 2019-3-29 15:51:19*/
      ]
    })
    

    接着,在\src\assets\theme目录下新增theme.less文件,这样就可以重载vux的样式了:

    /*提供重写Vux框架皮肤功能
    **add by LeoFeng 2019-3-29 15:51:19
    */
    @search-cancel-font-color: #80b1f0;
    @search-bg-color:#EFEFF4;
    @tabbar-text-active-color:#80b1f0;
    
    @header-background-color:#fff;
    @header-title-color:#333;
    @header-text-color:#333;
    

    五、业务开发


    六、项目部署


    七、总结

    相关文章

      网友评论

        本文标题:《基于Vue2.0的Web移动应用开发》- 基础

        本文链接:https://www.haomeiwen.com/subject/ytxpaftx.html