前言
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)系列
收獲
學(xué)習(xí)完這篇文章你將收獲:
- Vue + Vue-cli + iview + axios + vue-router + vuex 的實(shí)踐
- 高德地圖 js api的使用
- axios restful接口的異常處理封裝
- 上傳頭像
- modal彈框編輯個(gè)人信息template
- admin管理框架
[TOC]
正題
一、admin框架介紹
框架搭建了整體架構(gòu)欠气,單頁(yè)面的web應(yīng)用厅各。通用縮放式菜單欄,選項(xiàng)卡式管理網(wǎng)頁(yè)预柒、面包屑導(dǎo)航讯检、bug日志管理、全屏等功能卫旱。
框架使用了通用熱門(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í)定位功能主要邏輯為:
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)參看
關(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