GPS定位系統(tǒng)(四)——Vue前端

前言

GPS系列——Vue前端,github項(xiàng)目地址

前面已經(jīng)學(xué)習(xí)了Android、Java端的代碼實(shí)現(xiàn)御蒲,現(xiàn)在開(kāi)始介紹網(wǎng)站前端vue的管理框架缩举。

文中也會(huì)有大量代碼垦梆,對(duì)于admin管理框架,我是模仿iview-amin仅孩,然后新建一個(gè)項(xiàng)目托猩,手敲下來(lái)的,只取了自己所需的模塊辽慕,目的就是為了練手京腥,期間也遇到了很多問(wèn)題,建議大家也可以自己模仿者手敲一遍溅蛉。也可以使用elementUI公浪,這個(gè)框架整體而言比iview更好一些他宛。

GPS定位系統(tǒng)系列

GPS定位系統(tǒng)(一)——介紹

GPS定位系統(tǒng)(二)——Android端

GPS定位系統(tǒng)(三)——Java后端

GPS定位系統(tǒng)(四)——Vue前端

GPS定位系統(tǒng)(五)——Docker

收獲

學(xué)習(xí)完這篇文章你將收獲:

  • Vue + Vue-cli + iview + axios + vue-router + vuex 的實(shí)踐
  • 高德地圖 js api的使用
  • axios restful接口的異常處理封裝
  • 上傳頭像
  • modal彈框編輯個(gè)人信息template
  • admin管理框架

[TOC]

正題

一、admin框架介紹

主頁(yè)

框架搭建了整體架構(gòu)欠气,單頁(yè)面的web應(yīng)用厅各。通用縮放式菜單欄,選項(xiàng)卡式管理網(wǎng)頁(yè)预柒、面包屑導(dǎo)航讯检、bug日志管理、全屏等功能卫旱。

image-20200710144618941

框架使用了通用熱門(mén)的 VUE一套框架人灼,包括Vue + Vue-cli + vuex + vue-router+iview,具體還請(qǐng)參見(jiàn)源碼顾翼。

單頁(yè)面大致結(jié)構(gòu)

<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>
      <!--      展開(kāi)狀態(tài)-->
      <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個(gè)-->
          <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個(gè)嵌套-->
            <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>
            <!--            沒(méi)有children-->
            <template v-else>
              <MenuItem :name='item.name'>
                <Icon :type="item.meta.icon"></Icon>
                <Span>{{showTitle(item)}}</Span>
              </MenuItem>
            </template>

          </template>

        </template>
      </Menu>
      <!--      收縮狀態(tài)-->
      <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>

對(duì)于縮放菜單的功能較為復(fù)雜投放,可以細(xì)品一下,能收獲許多适贸。

二灸芳、axios封裝

我們的java后臺(tái)的接口統(tǒng)一數(shù)據(jù)為restful結(jié)構(gòu)的

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

對(duì)于axios而言封裝上面要注意其返回的respon的結(jié)構(gòu),以及異常response的結(jié)構(gòu)的處理拜姿。

這里先放整體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) {
    // 請(qǐng)求攔截
    instance.interceptors.request.use(config => {
      // 添加全局的loading...
      if (!Object.keys(this.queue).length) {
        // Spin.show() // 不建議開(kāi)啟烙样,因?yàn)榻缑娌挥押?      }
      this.queue[url] = true
      return config
    }, error => {
      return Promise.reject(error)
    })
    // 響應(yīng)攔截
    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('網(wǎng)絡(luò)出現(xiàn)問(wèn)題,請(qǐng)稍后再試')
      }
      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('登錄過(guò)期蕊肥,請(qǐng)重新登錄')
        break;
      // 404請(qǐng)求不存在
      case 404:
        Message.error('網(wǎng)絡(luò)請(qǐng)求不存在')
        break;
      // 其他錯(cuò)誤谒获,直接拋出錯(cuò)誤提示
      default:
        Message.error("系統(tǒng)錯(cuò)誤")
    }
  }

  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

調(diào)用:

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表單的形式,需要做轉(zhuǎn)化壁却,這里可以簡(jiǎn)單方便的使用Qs庫(kù)來(lái)直接stringify批狱,也別忘了設(shè)置headers的'Content-Type': 'application/x-www-form-urlencoded',因?yàn)閍xios默認(rèn)的是json格式展东。

1赔硫、interceptors的response編寫(xiě)

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":"請(qǐng)求成功"
    },
    "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接口請(qǐng)求的response的數(shù)據(jù)結(jié)構(gòu)如上盐肃。

可以看到獲取數(shù)據(jù)需要res.data.data爪膊,我們這里用解構(gòu) const {data: {code, data, msg}, config} = res 一下,code==200(其實(shí)是res.data.code)的時(shí)候返回data(其實(shí)就是返回res.data.data)

2砸王、error處理

這里的error可以分為3類:

  • 服務(wù)器端的業(yè)務(wù)的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('登錄過(guò)期推盛,請(qǐng)重新登錄')
        break;
      // 404請(qǐng)求不存在
      case 404:
        Message.error('網(wǎng)絡(luò)請(qǐng)求不存在')
        break;
      // 其他錯(cuò)誤,直接拋出錯(cuò)誤提示
      default:
        Message.error("系統(tǒng)錯(cuò)誤")
    }
  }

這里封裝一個(gè)方法处硬,用于處理服務(wù)器端的業(yè)務(wù)的http error前端的https error小槐,因?yàn)樗麄兘Y(jié)構(gòu)都是相同的。

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('網(wǎng)絡(luò)出現(xiàn)問(wèn)題,請(qǐng)稍后再試')
      }
      return Promise.reject(error)
    }

但是還有一種error也要處理凿跳,這里判斷如果error.response不為空件豌,則為http類型的error,使用dealErr控嗜,如果為空茧彤,則說(shuō)明是非http類型的err,直接toast 網(wǎng)絡(luò)出現(xiàn)問(wèn)題疆栏,請(qǐng)稍后再試(比如曾掂,前端跨域報(bào)錯(cuò),請(qǐng)求不符合規(guī)范等錯(cuò)誤壁顶,就是非http類型的err)

結(jié)構(gòu)如下

{
    "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
    }
}

三珠洗、高德地圖相關(guān)功能

1、引入sdk使用

1)在public文件夾下的index.html的<head>中加入 <script type="text/javascript" src="https://webapi.amap.com/maps?v=1.4.15&key=你的key"></script>

注意這里需要在body前面若专,不然有時(shí)候地圖加載不出來(lái)

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

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

2)vue文件的template中加入``<div id="map"></div>

注意map需要設(shè)置寬高

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

文件中引入mapUI

<script src="http://webapi.amap.com/ui/1.1/main.js"></script>
<script>
 import AMap from 'AMap'

3)使用

 const map = new AMap.Map('map', {
          resizeEnable: true,
          zoom: 11
        })
        this.map = map
        map.plugin(['AMap.ToolBar', 'AMap.MapType'], function () {
          map.addControl(new AMap.ToolBar())
          map.addControl(new AMap.MapType({showTraffic: false, showRoad: false}))
        })

注意:如果同一頁(yè)面使用多個(gè)地圖许蓖,需要div的id設(shè)置為不同,比如调衰,map1膊爪、map2等,Map()構(gòu)造也要傳入相應(yīng)的map id

不出意外嚎莉,地圖就能加載出來(lái)了米酬。

2、實(shí)時(shí)定位

實(shí)時(shí)定位

實(shí)時(shí)定位功能主要邏輯為:

1趋箩、從接口獲取所有人的最新的定位信息赃额,然后生成marker標(biāo)注在地圖上

2、并且給每個(gè)標(biāo)注都注入個(gè)人信息阁簸,如頭像爬早、名字、時(shí)間等信息

3启妹、可以下拉選擇用戶,定位到地圖中心醉旦,且打開(kāi)infoWindow展示個(gè)人信息

4饶米、由于接口是沒(méi)有存具體地址的,需要利用經(jīng)緯度坐標(biāo)车胡,轉(zhuǎn)為具體地址展示在infowindow上

詳情檬输,請(qǐng)參看源碼。

獲取接口數(shù)據(jù)匈棘,添加markers和dropdown

 allNowGps() {
        this.getAllNowGps().then(res => {
          this.data = res;
          this.addMarkers(res)
          this.addDropDown(res)
          console.log("res", JSON.stringify(res))
        })
      },
 addMarkers(data) {
        this.infoWindow = new AMap.InfoWindow({offset: new AMap.Pixel(0, -70)});
        data.forEach((item, index) => {
          var marker = new AMap.Marker({
            position: [item.lng, item.lat],
            icon: personLogo,
            offset: new AMap.Pixel(-15, -66),
            map: this.map,
            extData: item,
          });
          var info = this.getContentByItem(item);
          marker.content = info.join("<br/>") //使用默認(rèn)信息窗體框樣式丧慈,顯示信息內(nèi)容
          marker.on('mouseover', this.markerHover);
          // marker.emit('mouseover', {target: marker});
          // 真正的content的使用,是在hover打開(kāi)infowindow的時(shí)候,傳給indowindow進(jìn)行展示的
        })
        this.map.setFitView();
      },
addDropDown(res) {
        if (res && res.length > 0) {
          res.forEach(item => {
            //默認(rèn)獲取自己的
            //注意從cookie里面拿出來(lái)默認(rèn)是string
            if (item.uid == this.$store.state.user.userId) {
              this.currentUser = item
              //設(shè)為中心 顯示信息
              this.map.setCenter([item.lng, item.lat])
            }
          })
        }
      },

hover之后展示infowindow

 markerHover(e) {
        var _this = this
        //轉(zhuǎn)換經(jīng)緯度為具體地址
        this.geocoder.getAddress(e.target.getPosition(), function (status, result) {
          if (status === 'complete' && result.info === 'OK') {
            var address = result.regeocode.formattedAddress;
            console.dir(address);
            const content = e.target.content + '<br/>地址:' + address;
            const marker = e.target
            const item = e.target.getExtData()
            console.log('extData', e.target.getExtData());
            _this.infoWindow.setContent(content)
            _this.infoWindow.open(_this.map, e.target.getPosition());
          }
        });
      },

這里e.target其實(shí)就是marker逃默,然后marker的extData可以裝載 item的數(shù)據(jù)鹃愤,這里也取出來(lái)展示

注意,這里使用了geocoder來(lái)把經(jīng)緯度坐標(biāo)轉(zhuǎn)為具體地址

3完域、歷史軌跡

歷史軌跡

歷史軌跡功能主要邏輯為:

1软吐、默認(rèn)獲取當(dāng)前用戶的歷史軌跡數(shù)據(jù),可以通過(guò)日期篩選吟税,并生成軌跡和marker

2凹耙、獲取所有的用戶,生成選擇下拉列表肠仪,選擇下拉肖抱,可以獲取對(duì)應(yīng)用戶的歷史軌跡數(shù)據(jù)

3、利用高德地圖的繪制軌跡

4异旧、開(kāi)始marker沿著軌跡移動(dòng)意述,模擬移動(dòng)行為

 allUsers() {
        this.handleGetAllUsers().then(res => {
          console.log("users", JSON.stringify(res))
          let users = [];
          users = res;
          this.users = users
          if (res && users.length > 0) {
            users.forEach(item => {
              //默認(rèn)獲取自己的
              //注意從cookie里面拿出來(lái)默認(rèn)是string   == 就可以比較
              if (item.uid == this.$store.state.user.userId) {
                this.currentUser = item
                this.selectGpsHis(item.uid)
              }
            })
          }
        })
      },

獲取所有用戶

selectGpsHis(uid, from, to) {
        this.getGpsHis({uid, from, to}).then(data => {
          console.log("getGpsHis", JSON.stringify(data))
          this.showGpsHis(data)
        })
      },

獲取對(duì)應(yīng)用戶的歷史軌跡數(shù)據(jù)

showGpsHis(data) {
        this.map.clearMap()
        this.followPath = []

        data.forEach((item, index) => {
          const gps = [item.lng, item.lat]
          this.followPath.push(gps)
        })
        //重組數(shù)據(jù)為 [[lng,lat],[lng2,lat2]]

        if (this.followPath.length === 0) {
          Message.warning('無(wú)歷史數(shù)據(jù)')
          return
        }

        this.marker = new AMap.Marker({
          map: this.map,
          position: this.followPath[0],
          icon: personLogo,
          offset: new AMap.Pixel(-15, -66),
        });

        // 繪制軌跡
        var polyline = new AMap.Polyline({
          map: this.map,
          path: this.followPath,
          showDir: true,
          strokeColor: "#28F",  //線顏色
          // strokeOpacity: 1,     //線透明度
          strokeWeight: 6,      //線寬
          // strokeStyle: "solid"  //線樣式
        });

        var passedPolyline = new AMap.Polyline({
          map: this.map,
          // path: this.followPath,
          strokeColor: "#AF5",  //線顏色
          // strokeOpacity: 1,     //線透明度
          strokeWeight: 6,      //線寬
          // strokeStyle: "solid"  //線樣式
        });

        this.marker.on('moving', function (e) {
          passedPolyline.setPath(e.passedPath);
        });
        this.map.setFitView()
      },

繪制軌跡并且生成marker

 startAnimation() {
        this.marker.moveAlong(this.followPath, 5000);
      },
stopAnimation() {
          this.marker.stopMove()
        },

開(kāi)始軌跡、停止軌跡泽艘,軌跡速度是按照地圖的每小時(shí)多少千米的速度來(lái)設(shè)置的欲险。

注意:拿到的數(shù)據(jù),需要重組成 軌跡所需的數(shù)據(jù)結(jié)構(gòu)匹涮。

四天试、modal彈框template自定義

用戶管理

對(duì)于用戶管理,我們有創(chuàng)建和編輯用戶資料然低,兩個(gè)格式相同喜每,可以復(fù)用。這里自定義的modal模板雳攘,可以參考一下带兜。

代碼較長(zhǎng):

<template>
  <Modal :value="isShow" :title="title" @on-visible-change="handleVisible">
    <Upload
      ref="upload"
      :show-upload-list="false"
      :on-success="handleUploadSuccess"
      :format="['jpg','jpeg','png']"
      :max-size="2048"
      :on-format-error="handleUploadFormatError"
      :on-exceeded-size="handleUploadMaxSize"
      :headers="header"
      type="drag"
      :action="uploadUrl"
      style="display: inline-block;width:50px;height:50px;margin-bottom: 50px">
      <Avatar :src='user.avatar' style="width:50px;height: 50px"/>
    </Upload>
    <Form ref="user" :model="user" :rules="ruleValidate">
      <FormItem label="用戶名" prop="username">
        <Input v-model="user.username"/>
      </FormItem>
      <FormItem label="密碼" prop="password">
        <Input v-model="user.password"/>
      </FormItem>
      <FormItem label="姓名">
        <Input v-model="user.name"/>
      </FormItem>
      <FormItem label="手機(jī)" prop="mobile">
        <Input v-model="user.mobile"/>
      </FormItem>
    </Form>
    <div slot="footer">
      <Button size="large" @click="handleCancel">取消</Button>
      <Button type="primary" size="large" @click="handleConfirm('user')">確定</Button>
    </div>
  </Modal>
</template>
<script>
  export default {
    name: 'edit-user',
    props: {
      //姓名、頭像吨灭、手機(jī)刚照、用戶名、密碼喧兄、
      user: {
        uid: '',
        avatar:'',
        name: '',
        username: '',
        password:null,
        mobile: '',
      },
      //是否顯示彈框
      isShow: false,
      //上傳所需的token
      header: {},
      //上傳地址
      uploadUrl: {},
      isEdit: {
        type: Boolean
      },
    },
    computed: {
      title() {
        return this.isEdit === true ? "修改用戶信息" : "新建用戶信息"
      },
      ruleValidate() {
        console.log('ruleValidate', this.isEdit)
        const rule = this.isEdit === true ? this.editRuleValidate : this.createRuleValidate
        console.log('ruleValidate', rule)
        return rule
      }
    },
    watch:{
      isShow(val){
        console.log('isShow',val)
      }
    },
    data() {
      return {
        editRuleValidate: {
          username: [
            {required: true, message: 'The name cannot be empty', trigger: 'blur'}
          ],
          mobile:[
            { required: false, message: "請(qǐng)輸入手機(jī)號(hào)碼", trigger: "blur" },
            { pattern: /^1[3456789]\d{9}$/, message: "手機(jī)號(hào)碼格式不正確", trigger: "blur"}
          ]
        },
        createRuleValidate: {
          username: [
            {required: true, message: 'The name cannot be empty', trigger: 'blur'}
          ],
          password: [
            {required: true, message: 'The password cannot be empty', trigger: 'blur'}
          ],
          mobile:[
            { required: false, message: "請(qǐng)輸入手機(jī)號(hào)碼", trigger: "blur" },
            { pattern: /^1[3456789]\d{9}$/, message: "手機(jī)號(hào)碼格式不正確", trigger: "blur"}
          ]
        }
      }
    },
    methods: {
      handleUploadSuccess(res, file) {
        console.log(file.response)
        this.user.avatar = file.response
      },
      handleUploadFormatError(file) {
        this.$Notice.warning({
          title: 'The file format is incorrect',
          desc: 'File format of ' + file.name + ' is incorrect, please select jpg or png.'
        });
      },
      handleUploadMaxSize(file) {
        this.$Notice.warning({
          title: 'Exceeding file size limit',
          desc: 'File  ' + file.name + ' is too large, no more than 2M.'
        });
      },
      //處理確定
      handleConfirm(name) {
        this.$refs[name].validate((valid) => {
          console.log('handleConfirm validate',valid)
          if (valid) {
            //發(fā)送ok事件
            this.$emit('ok', {user: this.user, isEdit: this.isEdit})
            //關(guān)閉彈框
            this.$emit('visible', false)
          }
        })
      },
      handleCancel() {
        //關(guān)閉彈框
        this.$emit('visible', false)
      },
      handleVisible(visible) {
        //每次都清空驗(yàn)證信息 因?yàn)榫庉嫼蛣?chuàng)建不一樣
        this.$refs['user'].resetFields();
        //發(fā)送事件給父組件 修改自己的visible狀態(tài)(注意這里 不能用v-model數(shù)據(jù)綁定 子組件不能修改父組件傳來(lái)的prop的對(duì)象狀態(tài))
        this.$emit('visible', visible)
      }
    },
  };
</script>

使用:

<div v-if="isShowEdit">
      <EditUser :user="newUser"
                :isEdit="isEdit"
                @visible="this.handleEditVisible"
                                :is-show="true"
                :header="{'token': this.$store.state.user.token}"
                :upload-url="this.$config.baseUrl + 'upload'"
                @ok="handleEditOk"
      ></EditUser>
    </div>

通過(guò)isEdit去判別是編輯還是新建

這里有一點(diǎn)比較重要:

isShowEdit是用于我們動(dòng)態(tài)去展示和隱藏modal彈框无畔,使用的是v-if。網(wǎng)上很多人吠冤,是使用modal的v-model或者value來(lái)控制modal的顯示和隱藏的浑彰,原本我也是那樣做的,但是后來(lái)發(fā)現(xiàn)拯辙,那樣做非常不穩(wěn)定和可靠郭变,有時(shí)候彈框彈出來(lái),其雙向綁定的is-show,并未能和modal的狀態(tài)統(tǒng)一诉濒。所以周伦,最終使用的是div+v-if的方式來(lái)控制。

還有就是循诉,關(guān)于父組件和子組件之間傳值的問(wèn)題:

父給子横辆,一般是通過(guò)props來(lái)接收,并且茄猫,我們希望的是單向的狈蚤,父可以改變控制子,但是划纽,子不能改變?nèi)タ刂聘复辔辏蝗粫?huì)報(bào)錯(cuò)。而需要使用勇劣,子發(fā)事件回調(diào)給父靖避,來(lái)改變父的狀態(tài)的方式來(lái)實(shí)現(xiàn)。

//處理確定
      handleConfirm(name) {
        this.$refs[name].validate((valid) => {
          console.log('handleConfirm validate',valid)
          if (valid) {
            //發(fā)送ok事件
            this.$emit('ok', {user: this.user, isEdit: this.isEdit})
            //關(guān)閉彈框
            this.$emit('visible', false)
          }
        })
      },
      
 handleVisible(visible) {
        //每次都清空驗(yàn)證信息 因?yàn)榫庉嫼蛣?chuàng)建不一樣
        this.$refs['user'].resetFields();
        //發(fā)送事件給父組件 修改自己的visible狀態(tài)(注意這里 不能用v-model數(shù)據(jù)綁定 子組件不能修改父組件傳來(lái)的prop的對(duì)象狀態(tài))
        this.$emit('visible', visible)
      }

this.$emit('visible', false) 使用這個(gè)來(lái)改變其父的isShowEdit的值比默,從而隱藏或者顯示自身modal幻捏。

上傳頭像

<Upload
      ref="upload"
      :show-upload-list="false"
      :on-success="handleUploadSuccess"
      :format="['jpg','jpeg','png']"
      :max-size="2048"
      :on-format-error="handleUploadFormatError"
      :on-exceeded-size="handleUploadMaxSize"
      :headers="header"
      type="drag"
      :action="uploadUrl"
      style="display: inline-block;width:50px;height:50px;margin-bottom: 50px">
      <Avatar :src='user.avatar' style="width:50px;height: 50px"/>
    </Upload>

上傳頭像注意下,如果我們上傳圖標(biāo)需要header命咐,比如傳token的話篡九,需要把header作為參數(shù)傳進(jìn)來(lái)。

五醋奠、登錄驗(yàn)證

對(duì)于登錄驗(yàn)證榛臼,我們這邊是使用vue-router的beforeEach統(tǒng)一處理頁(yè)面的跳轉(zhuǎn)來(lái)實(shí)現(xiàn)的。

const router = new Router({
  routes: routers,
  mode: 'history',
})
const turnTo = (to, access, next) => {
  // if (canTurnTo(to.name, access, routes)) next() // 有權(quán)限窜司,可訪問(wèn)
  // else next({replace: true, name: 'error_401'}) // 無(wú)權(quán)限沛善,重定向到401頁(yè)面
  next()
}
router.beforeEach((to, from, next) => {
  iView.LoadingBar.start()
  const token = getToken()
  if (!token && to.name !== LOGIN_PAGE_NAME) {
    // 未登錄且要跳轉(zhuǎn)的頁(yè)面不是登錄頁(yè)
    next({
      name: LOGIN_PAGE_NAME // 跳轉(zhuǎn)到登錄頁(yè)
    })
  } else if (!token && to.name === LOGIN_PAGE_NAME) {
    // 未登陸且要跳轉(zhuǎn)的頁(yè)面是登錄頁(yè)
    next() // 跳轉(zhuǎn)
  } else if (token && to.name === LOGIN_PAGE_NAME) {
    // 已登錄且要跳轉(zhuǎn)的頁(yè)面是登錄頁(yè)
    next({
      name: homeName // 跳轉(zhuǎn)到homeName頁(yè)
    })
  } else {
    //這由于暫時(shí)沒(méi)有權(quán)限系統(tǒng) 直接 跳轉(zhuǎn)即可
    // if (store.state.user.hasGetInfo) {
      turnTo(to, store.state.user.access, next)
    // } else {
    //   store.dispatch('getUserInfo').then(user => {
    //     // 拉取用戶信息,通過(guò)用戶權(quán)限和跳轉(zhuǎn)的頁(yè)面的name來(lái)判斷是否有權(quán)限訪問(wèn);access必須是一個(gè)數(shù)組塞祈,如:['super_admin'] ['super_admin', 'admin']
    //     turnTo(to, user.access, next)
    //   }).catch(() => {
    //     setToken('')
    //     next({
    //       name: 'login'
    //     })
    //   })
    // }
  }
})

router.afterEach(to => {
  //設(shè)置標(biāo)題
  setTitle(to, router.app)
    //隱藏進(jìn)度條
  iView.LoadingBar.finish()
  window.scrollTo(0, 0)
})

總結(jié)

源碼很多金刁,所以其實(shí)很多東西都在源碼里面了,可能內(nèi)容篇幅較長(zhǎng)议薪,很少有人能夠完整看完胀葱,但是,寫(xiě)在這里只為某些時(shí)候可能遇到類似問(wèn)題笙蒙,有一個(gè)借鑒參考的地方即可。就如同庆锦,我自己手敲admin框架的時(shí)候捅位,很多時(shí)候iview-amin就是我的一個(gè)可以借鑒和參考的項(xiàng)目,遇到有不會(huì)的或者沒(méi)有思路的,就可以參考借鑒一下艇搀,這樣會(huì)好很多尿扯。

整個(gè)系列,前端焰雕、移動(dòng)端衷笋、后端,都有了矩屁,打通了辟宗,接下來(lái)就是要學(xué)習(xí)一下,怎么打包吝秕,部署到服務(wù)器那些東西了泊脐。

服務(wù)器呢,我打算再使用docker烁峭,學(xué)習(xí)一番docker+nginx+mysql等實(shí)現(xiàn)前端和后端的線上部署容客,具體請(qǐng)參看

GPS定位系統(tǒng)(五)——Docker

關(guān)于作者

作者是一個(gè)熱愛(ài)學(xué)習(xí)、開(kāi)源约郁、分享缩挑,傳播正能量,喜歡打籃球鬓梅、頭發(fā)還很多的程序員-供置。-

熱烈歡迎大家關(guān)注、點(diǎn)贊己肮、評(píng)論交流士袄!

簡(jiǎn)書(shū):http://www.reibang.com/u/d234d1569eed

github:https://github.com/fly7632785

CSDN:https://blog.csdn.net/fly7632785

掘金:https://juejin.im/user/5efd8d205188252e58582dc7/posts

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市谎僻,隨后出現(xiàn)的幾起案子娄柳,更是在濱河造成了極大的恐慌,老刑警劉巖艘绍,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赤拒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡诱鞠,警方通過(guò)查閱死者的電腦和手機(jī)挎挖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)航夺,“玉大人蕉朵,你說(shuō)我怎么就攤上這事⊙羝” “怎么了始衅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵冷蚂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我汛闸,道長(zhǎng)蝙茶,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任诸老,我火速辦了婚禮隆夯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘别伏。我一直安慰自己蹄衷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布畸肆。 她就那樣靜靜地躺著宦芦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪轴脐。 梳的紋絲不亂的頭發(fā)上调卑,一...
    開(kāi)封第一講書(shū)人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音大咱,去河邊找鬼恬涧。 笑死,一個(gè)胖子當(dāng)著我的面吹牛碴巾,可吹牛的內(nèi)容都是我干的溯捆。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼厦瓢,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼提揍!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起煮仇,我...
    開(kāi)封第一講書(shū)人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤劳跃,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后浙垫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體刨仑,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年夹姥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了杉武。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辙售,死狀恐怖轻抱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情旦部,我是刑警寧澤十拣,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布封拧,位于F島的核電站,受9級(jí)特大地震影響夭问,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜曹铃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一缰趋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧陕见,春花似錦秘血、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至忍坷,卻和暖如春粘舟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背佩研。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工柑肴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人旬薯。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓晰骑,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親绊序。 傳聞我的和親對(duì)象是個(gè)殘疾皇子硕舆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345