GPS定位系统(四)——Vue前端

前言

GPS系列——Vue前端,github项目地址

前面已经学习了Android、Java端的代码实现,现在开始介绍网站前端vue的管理框架。

文中也会有大量代码,对于admin管理框架,我是模仿iview-amin,然后新建一个项目,手敲下来的,只取了自己所需的模块,目的就是为了练手,期间也遇到了很多问题,建议大家也可以自己模仿者手敲一遍。也可以使用elementUI,这个框架整体而言比iview更好一些。

GPS定位系统系列

GPS定位系统(一)——介绍

GPS定位系统(二)——Android端

GPS定位系统(三)——Java后端

GPS定位系统(四)——Vue前端

GPS定位系统(五)——Docker

  • Docker nginx 二级域名无端口访问多个web项目
  • Docker nginx https二级域名无端口访问多个web项目
  • 持续部署——Travis+Docker+阿里云容器镜像

收获

学习完这篇文章你将收获:

  • Vue + Vue-cli + iview + axios + vue-router + vuex 的实践
  • 高德地图 js api的使用
  • axios restful接口的异常处理封装
  • 上传头像
  • modal弹框编辑个人信息template
  • admin管理框架

文章目录

  • 前言
      • GPS定位系统系列
      • 收获
  • 正题
    • 一、admin框架介绍
    • 二、axios封装
      • 1、interceptors的response编写
      • 2、error处理
    • 三、高德地图相关功能
      • 1、引入sdk使用
      • 2、实时定位
      • 3、历史轨迹
    • 四、modal弹框template自定义
      • 上传头像
    • 五、登录验证
  • 总结
  • 关于作者

正题

一、admin框架介绍

框架搭建了整体架构,单页面的web应用。通用缩放式菜单栏,选项卡式管理网页、面包屑导航、bug日志管理、全屏等功能。

GPS定位系统(四)——Vue前端_第1张图片

框架使用了通用热门的 VUE一套框架,包括Vue + Vue-cli + vuex + vue-router+iview,具体还请参见源码。

单页面大致结构

<template>
  <Layout style="height: 100%" class="main">
    <Sider ref="sider" class="sider" hide-trigger collapsible :collapsed-width="78" v-model="isCollapsed">
      <div class="logo-con">
        <img v-show="!isCollapsed" :src="maxLogo" class="max-logo" key="max-logo"/>
        <img v-show="isCollapsed" :src="minLogo" class="min-logo" key="min-logo"/>
      </div>
      <!--      展开状态-->
      <Menu class="open-menu" ref="menu" :active-name="$route.name" :open-names="openedNames" theme="dark" width="auto"
            v-show="!isCollapsed"
            @on-select="turnToPage">
        <template v-for="item in menuList">
          <!--          有children且只有1-->
          <template v-if="item.children && item.children.length===1">
            <MenuItem :name='item.children[0].name'>
              <Icon :type="item.children[0].meta.icon"></Icon>
              <span>{{showTitle(item.children[0])}}</span>
            </MenuItem>
          </template>
          <template v-else>
            <!--            有children 大于1个嵌套-->
            <template v-if="item.children && item.children.length>1">
              <Submenu :name='item.name'>
                <template slot="title">
                  <Icon :type="item.meta.icon || ''"/>
                  <Span>{{showTitle(item) }}</Span>
                </template>
                <template v-for="subitem in item.children">
                  <MenuItem :name="subitem.name">
                    <Icon :type="subitem.meta.icon"></Icon>
                    <Span>{{showTitle(subitem)}}</Span>
                  </MenuItem>
                </template>
              </Submenu>
            </template>
            <!--            没有children-->
            <template v-else>
              <MenuItem :name='item.name'>
                <Icon :type="item.meta.icon"></Icon>
                <Span>{{showTitle(item)}}</Span>
              </MenuItem>
            </template>

          </template>

        </template>
      </Menu>
      <!--      收缩状态-->
      <div v-show="isCollapsed" class="close-menu">
        <template v-for="item in menuList">
          <template v-if="item.children && item.children.length>0">
            <Dropdown placement="right-start" @on-click="turnToPage" class='dropdown'>
              <a type="text" class="drop-menu-a">
                <Icon :type="item.meta.icon"></Icon>
              </a>
              <template v-for="subitem in item.children">
                <DropdownMenu slot="list">
                  <DropdownItem :name="subitem.name">
                    <a type="text" class="drop-item-a">
                      <Icon :type="subitem.meta.icon"></Icon>
                      <span>{{showTitle(subitem)}}</span>
                    </a>
                  </DropdownItem>
                </DropdownMenu>
              </template>
            </Dropdown>
          </template>
          <template v-else>
            <Tooltip transfer placement="right" :content="showTitle(item)">
              <a @click="turnToPage(item.name)" type="text" class="drop-menu-a">
                <Icon :type="item.meta.icon"></Icon>
              </a>
            </Tooltip>
          </template>
        </template>
      </div>
    </Sider>
    <layout>
      <Header class="header" :style="{padding:0}">

        <Icon @click.native="collapsedSider" :class="rotateIcon" :style="{margin:'0 20px'}" type='md-menu'
              size="24"></Icon>
        <custom-bread-crumb show-icon style="margin-left: 30px;" :list="breadCrumbList"></custom-bread-crumb>
        <div class="header-right">
          <user :message-unread-count="0" :user-avatar="userAvatar" :user-name="userName"/>
          <error-store v-if="$config.plugin['error-store'] && $config.plugin['error-store'].showInHeader"
                       :has-read="hasReadErrorPage" :count="errorCount"></error-store>
          <fullscreen v-model="isFullscreen" style="margin-right: 10px;"/>
        </div>
      </Header>
      <Content class="main-content-con">
        <Layout class="main-layout-con">
          <div class="tag-nav-wrapper">
            <tags-nav :value="$route" @input="handleClick" :list="tagNavList" @on-close="handleCloseTag"></tags-nav>
          </div>
          <Content class="content-wrapper">
            <keep-alive>
              <router-view/>
            </keep-alive>
          </Content>
        </Layout>
      </Content>
      <!--      <Footer class="footer">Footer</Footer>-->
    </layout>
  </Layout>
</template>

对于缩放菜单的功能较为复杂,可以细品一下,能收获许多。

二、axios封装

我们的java后台的接口统一数据为restful结构的

{code:xxx,msg:xxx,data:xxx}

对于axios而言封装上面要注意其返回的respon的结构,以及异常response的结构的处理。

这里先放整体axios封装代码:

axios.js

import axios from 'axios'
import store from '@/store'
import {Message} from 'iview'

// import { Spin } from 'iview'
const addErrorLog = errorInfo => {
  const {statusText, status, request: {responseURL}} = errorInfo
  let info = {
    type: 'ajax',
    code: status,
    mes: statusText,
    url: responseURL
  }
  console.log("addErr:" + JSON.stringify(info))
  if (!responseURL.includes('save_error_logger')) store.dispatch('addErrorLog', info)
}

class HttpRequest {
  constructor(baseUrl = baseURL) {
    this.baseUrl = baseUrl
    this.queue = {}
  }

  getInsideConfig() {
    const config = {
      baseURL: this.baseUrl,
      // headers: {
      //   'Content-Type': "application/json;charset=utf-8"
      // }
    }
    return config
  }

  destroy(url) {
    delete this.queue[url]
    if (!Object.keys(this.queue).length) {
      // Spin.hide()
    }
  }


  interceptors(instance, url) {
    // 请求拦截
    instance.interceptors.request.use(config => {
      // 添加全局的loading...
      if (!Object.keys(this.queue).length) {
        // Spin.show() // 不建议开启,因为界面不友好
      }
      this.queue[url] = true
      return config
    }, error => {
      return Promise.reject(error)
    })
    // 响应拦截
    instance.interceptors.response.use(res => {
      console.log("res:" + JSON.stringify(res))
      this.destroy(url)
      const {data: {code, data, msg}, config} = res
      if (code == 200) {
        return data;
      } else {
        this.dealErr(code, msg)
        let errorInfo = {
          statusText: msg,
          status: code,
          request: {responseURL: config.url}
        }
        addErrorLog(errorInfo)
        return Promise.reject(res.data)
      }
    }, error => {
      console.log("error:" + JSON.stringify(error))
      this.destroy(url)
      let errorInfo = error.response
      if (!typeof(errorInfo) === undefined && !errorInfo) {
        const {request: {statusText, status}, config} = JSON.parse(JSON.stringify(error))
        errorInfo = {
          statusText,
          status,
          request: {responseURL: config.url}
        }
        addErrorLog(errorInfo)
        const data = {code: status, msg: statusText}
        this.dealErr(data.code, data.msg)
      } else {
        Message.error('网络出现问题,请稍后再试')
      }
      return Promise.reject(error)
    })
  }


  dealErr(c, msg) {
    console.log("code:" + c)
    console.log("msg:" + msg)
    switch (c) {
      case 400:
        Message.error(msg)
        break;
      case 401:
        Message.error('登录过期,请重新登录')
        break;
      // 404请求不存在
      case 404:
        Message.error('网络请求不存在')
        break;
      // 其他错误,直接抛出错误提示
      default:
        Message.error("系统错误")
    }
  }

  request(options) {
    const instance = axios.create()
    options = Object.assign(this.getInsideConfig(), options)
    this.interceptors(instance, options.url)
    return instance(options)
  }


}

export default HttpRequest

api.request.js:

import HttpRequest from '@/libs/axios'
import config from '@/config'
const baseUrl = config.baseUrl

const axios = new HttpRequest(baseUrl)
export default axios

调用:

import axios from '@/libs/api.request'
import Qs from 'qs'

export const login = ({username, password}) => {
  const data = {
    username,
    password
  }
  return axios.request({
    url: 'login',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    data: Qs.stringify(data),
    method: 'post'
  })
}

注意:如果要使用form表单的形式,需要做转化,这里可以简单方便的使用Qs库来直接stringify,也别忘了设置headers的'Content-Type': 'application/x-www-form-urlencoded',因为axios默认的是json格式。

1、interceptors的response编写

res => {
      console.log("res:" + JSON.stringify(res))
      this.destroy(url)
      const {data: {code, data, msg}, config} = res
      if (code == 200) {
        return data;
      } else {
        this.dealErr(code, msg)
        let errorInfo = {
          statusText: msg,
          status: code,
          request: {responseURL: config.url}
        }
        //添加到日志
        addErrorLog(errorInfo)
        return Promise.reject(res.data)
      }
    }
{
    "data":{
        "code":200,
        "data":{
            xxxx
        },
        "msg":"请求成功"
    },
    "status":200,
    "statusText":"",
    "headers":{
        "content-length":"487",
        "content-type":"application/json;charset=UTF-8"
    },
    "config":{
        "url":"http://127.0.0.1:9090/get_info",
        "method":"post",
        "headers":{
            "Accept":"application/json, text/plain, */*",
            "token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpbk5hbWUiOiJrayIsImV4cCI6MTU5Njk1Nzg1MSwidXNlcklkIjoiMTMifQ.ChaBg4n5KKsF7ISj8uzHV0eh_JKadoVIBtNG4oUtp8U"
        },
        "baseURL":"http://127.0.0.1:9090/",
        "transformRequest":[
            null
        ],
        "transformResponse":[
            null
        ],
        "timeout":0,
        "xsrfCookieName":"XSRF-TOKEN",
        "xsrfHeaderName":"X-XSRF-TOKEN",
        "maxContentLength":-1
    },
    "request":{

    }
}

注意,axios接口请求的response的数据结构如上。

可以看到获取数据需要res.data.data,我们这里用解构 const {data: {code, data, msg}, config} = res 一下,code==200(其实是res.data.code)的时候返回data(其实就是返回res.data.data)

2、error处理

这里的error可以分为3类:

  • 服务器端的业务的http error
  • 前端的http error
  • 前端http error
dealErr(c, msg) {
    console.log("code:" + c)
    console.log("msg:" + msg)
    switch (c) {
      case 400:
        Message.error(msg)
        break;
      case 401:
        Message.error('登录过期,请重新登录')
        break;
      // 404请求不存在
      case 404:
        Message.error('网络请求不存在')
        break;
      // 其他错误,直接抛出错误提示
      default:
        Message.error("系统错误")
    }
  }

这里封装一个方法,用于处理服务器端的业务的http error前端的https error,因为他们结构都是相同的。

error => {
      console.log("error:" + JSON.stringify(error))
      this.destroy(url)
      let errorInfo = error.response
      if (!typeof(errorInfo) === undefined && !errorInfo) {
        const {request: {statusText, status}, config} = JSON.parse(JSON.stringify(error))
        errorInfo = {
          statusText,
          status,
          request: {responseURL: config.url}
        }
        addErrorLog(errorInfo)
        const data = {code: status, msg: statusText}
        this.dealErr(data.code, data.msg)
      } else {
        Message.error('网络出现问题,请稍后再试')
      }
      return Promise.reject(error)
    }

但是还有一种error也要处理,这里判断如果error.response不为空,则为http类型的error,使用dealErr,如果为空,则说明是非http类型的err,直接toast 网络出现问题,请稍后再试(比如,前端跨域报错,请求不符合规范等错误,就是非http类型的err)

结构如下

{
    "message":"Network Error",
    "name":"Error",
    "stack":"createError handleError",
    "config":{
        "url":"http://127.0.0.1:9090/login",
        "method":"post",
        "data":"username=kk&password=kk",
        "headers":{
            "Accept":"application/json, text/plain, */*",
            "Content-Type":"application/x-www-form-urlencoded"
        },
        "baseURL":"http://127.0.0.1:9090/",
        "transformRequest":[
            null
        ],
        "transformResponse":[
            null
        ],
        "timeout":0,
        "xsrfCookieName":"XSRF-TOKEN",
        "xsrfHeaderName":"X-XSRF-TOKEN",
        "maxContentLength":-1
    }
}

三、高德地图相关功能

1、引入sdk使用

**1)**在public文件夹下的index.html的中加入

注意这里需要在body前面,不然有时候地图加载不出来

vue.config.js配置文件中加入

module.exports = {
  configureWebpack: {
    externals: {
      'AMap': 'AMap',
      'AMapUI': 'AMapUI'
    },
  },
}

**2)**vue文件的template中加入``

注意map需要设置宽高

#map{
  width: 100%;
  height: 100%;
}

文件中引入mapUI



                    
                    

你可能感兴趣的:(GPS定位系统)