使用vue构建移动端SPA
易学堂应用内嵌网页开发的实践和总结
目录:
- 为什么选择vue及vue是什么
- vue的常用语法
- 业务流程梳理及页面组件划分
- 开发环境搭建及用到的vue插件和工具
- 实际开发中组件的数据传递与事件通信
- 遇到的问题及解决思路
- 致谢
一般做法
- 对应若干页面,由后端配置路由实现页面加载。
- 全部重新渲染,遇到网络和服务器问题,页面不可操作
- 前后端的逻辑混杂在一起
- 有利于SEO
单页应用
- 只加载一个页面,由前端负责页面路由处理及加载。
- 部分渲染,在遇到网络或服务器问题,不影响操作
- 前后端分离,前端负责页面呈现,后端负责数据操作
- 不利于SEO
为什么选择vue及vue是什么
数据驱动的组件,为现代化的 Web 界面而生
Vue.js
API文档
优点
有中文文档
有可运行的官方示例
有详细的中文文档
vue的常用语法
-
单文件组件(.vue)
<template lang="html">
<div class="gameinfo">
</div>
</template>
<script>
export default {
data: function () {
return {
}
},
computed: {},
ready: function () {
console.log(this.$route.query);
},
attached: function () {},
methods: {},
components: {}
}
</script>
<style lang="css" scoped>
</style>
渲染模版
数据绑定
事件绑定
加载资源
方法定义
组件拼装
定义样式
scoped 为组件样式
lang属性可以指定自己喜欢模板引擎和css预处理器,记得安装相应的webpack loader
关于组件命名:推荐使用小写加短线的方式:xx-xxx.vue
在template中推荐在最外层加一容器,也就是一个组件只返回一个dom节点
-
模板指令
{{}} 数据替换(可使用js表达式)
v-for 迭代数组和对象 (从 1.0.17 开始可以使用 of 分隔符,更接近 JavaScript 遍历器语法:)
<ul>
<li v-for='item in myGameList'>
<p id=‘{{$index}}’>
<span>{{item.gamename}}</span>
<span>{{item.gameState}}</span>
</p>
</li>
</ul>
<div v-for="(index, item) in items">
{{ index }} {{ item.message }}
</div>
v-if || v-show v-else
<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>
<h1 v-show="ok">Yes</h1>
<h1 v-else>No</h1>
v-if:真正的条件渲染 v-show:单纯的display切换
v-on:事件处理函数绑定 简写 ”@“
v-bind v-on
v-bind :特殊属性的数据绑定 简写 “:”
特殊属性包括:src href class style
<div class="static" v-bind:class="{ 'class-a':true }" v-on:click="eventhandle">
<a v-bind:href="url" v-bind:style="{ color:'#000'">
<img v-bind:src='userpic' alt="" />
</a>
</div>
<div class="static" :class="{ 'class-a':true }" @click="eventhandle">
<a :href="url" :style="{ color:'#000'">
<img :src='userpic' alt="" />
</a>
</div>
修饰符,过滤器
修饰符 (Modifiers) 是以半角句号 . 开始的特殊后缀,用于表示指令应当以特殊方式绑定。
<!-- 阻止单击事件冒泡 -->
<a v-on:click.stop="doThis"></a>
<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>
<!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat">
<!-- 只有修饰符 -->
<form v-on:submit.prevent></form>
<!-- 添加事件侦听器时使用 capture 模式 -->
<div v-on:click.capture="doThis">...</div>
<!-- 只当事件在该元素本身(而不是子元素)触发时触发回调 -->
<div v-on:click.self="doThat">...</div>
事件修饰符
按键修饰符(enter,tab,delete,esc,space,up,down,left,right)
<!-- 只有在 keyCode 是 13 时调用 vm.submit() -->
<input v-on:keyup.13="submit">
<!-- 同上 -->
<input v-on:keyup.enter="submit">
<!-- 缩写语法 -->
<input @keyup.enter="submit">
过滤器
数据处理,以管道符 | 连接 ,可以串联使用,前一个的输出是后一个的输入 ,vue自身提供了常用的过滤器。可以自己定义过滤器。http://vuejs.org.cn/api/#过滤器
{{ msg | uppercase }} //abc=> Abc
{{ message | filterA | filterB }} //串联使用
{{ message | filterA 'arg1' arg2 }} //接收参数
/**
过滤器函数始终以表达式的值作为第一个参数。
带引号的参数视为字符串,而不带引号的参数按表达式计算.
**/
data
-
脚本逻辑
Vue 实例的数据对象。可以是对象或者函数,推荐使用函数返回一个对象,如果单纯是一个对象那么多个组件实例共享同一个对象
data: function () {
return {
msg:“xx”
}
}
props
包含一些特性——期望使用的父组件数据的属性。可以是数组或对象。对象用于高级配置,如类型检查,自定义验证,默认值等。
// 简单语法
props: ['size', 'myMessage']
// 对象语法,指定验证要求
props: {
// 只检测类型
size: Number,
// 检测类型 + 其它验证
name: {
type: String,
required: true,
// 双向绑定
twoWay: true
}
}
methods
实例方法。实例可以直接访问这些方法,也可以用在指令表达式内。方法的 this 自动绑定到实例。
methods: {
method1:function (args) {
},
method2:function (args) {
}
}
ready
在编译结束和 $el 第一次插入文档之后调用,如在第一次 attached 钩子之后调用。注意必须是由 Vue 插入(如 vm.$appendTo() 等方法或指令更新)才触发 ready 钩子。
这个方法并不能保证dom结构渲染完毕。但可以进行dom操作
ready: function () {
//dom 操作
}
events
一个对象,键是监听的事件,值是相应的回调。注意这些事件是 Vue 事件而不是 DOM 事件。值也可以是方法的名字。在实例化的过程中,Vue 实例会调用对象的每个键。
components
用于引入子组件
events: {
'hook:created': function () {
console.log('created!')
},
greeting: function (msg) {
console.log(msg)
},
// 也可以是方法的名字
bye: 'sayGoodbye'
},
methods: {
sayGoodbye: function () {
console.log('goodbye!')
}
}
-
样式
注意使用 scoped 属性 ,他表示样式为组件样式,不会影响其他组件的样式,但会影响其子组件的样式。
<style lang="css" scoped>
//css 样式
</style>
业务流程梳理及页面组件划分
组件划分:
入口主组件:Hello.vue
全部比赛:all-list.vue
我参加的:my-list.vue
比赛信息:game-info.vue
通用的消息组件:msg.vue
Hello.vue
all-
list.vue
my-
list.vue
game-info.vue
msg.vue
划分原则:
一个页面对应一个路由(url链接)对应一个主组件。
在主组件内部根据内容相关程度划分子组件
开发环境搭建及用到的vue插件
- 移动端屏幕适配
normalize.css:标准化html元素的显示样式
- 开发环境搭建
vue-cli:用于生成vue项目的命令行工具
mocksever:用于模拟接口数据
- vue插件
vue-router:Vue.js 官方路由。与 Vue.js 内核深度整合,让构建单页应用易如反掌。
vue-resource:通过 XMLHttpRequest 或 JSONP 发起请求并处理响应。
vue-async-data:异步加载数据插件。
vue-devtools:Chrome 开发者工具扩展,用于调试 Vue.js 应用。
- 修改配置
var path = require('path')
module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/yxt/',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css']
},
dev: {
env: require('./dev.env'),
assetsPublicPath: '/',
port: 3000,
proxyTable: {
"/yxt":{
target:'http://localhost:8000',
changeOrigin:true
}
}
}
}
config/index.js
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>模拟炒股大赛</title>
<link rel="stylesheet" href="/static/css/normalize.css" />
<script type="text/javascript" src='/static/js/flexible.js'></script>
</head>
<body>
<div id="app">
</div>
<!-- built files will be auto injected -->
</body>
</html>
index.html
实际开发中组件的数据传递与事件通信
-
文件主入口(/src/main.js)
import Vue from 'vue'
import App from './App'
import VueResource from 'vue-resource'
import router from './router'
Vue.use(VueResource)
/* eslint-disable no-new */
router.start(App, '#app')
Vue.filter('toDate',function(sdata,edata){
return (new Date(sdata).getTime()- new Date(edata).getTime())/(1000*60*60*24);
})
Vue.filter('statusto',function(status){
var statustring='',
showmsg='';
switch (status-0){
case 1:
statustring='报名未开始'
showmsg='敬请期待'
break
case 2:
statustring='报名结束'
showmsg='报名结束'
break
case 3:
statustring='报名中'
showmsg='马上参与'
break
case 4:
statustring='比赛进行中'
showmsg='比赛中'
break
case 5:
statustring='比赛未开始'
showmsg='热身中'
break
case 6:
statustring='比赛结束'
showmsg='比赛结束'
break
case 7:
statustring='比赛进行中'
showmsg='审核中'
break
case 8:
statustring='比赛进行中'
showmsg='审核未通过'
break
default:
statustring='xxx'
showmsg='xxx'
}
return [statustring,showmsg]
})
Vue.filter('arrtoone',function(arr,index){
if(Array.isArray(arr)){
return arr[index];
}
return arr
})
-
路由配置(/src/router.js)
import Vue from 'vue'
import VueRouter from 'vue-router'
// import HomePage from './components/Hello'
Vue.use(VueRouter)
var router = new VueRouter({
history: true,
root:'/yxt/trade/match/'
})
router.map({
'/all': {
component:function (resolve) {
require(['./components/Hello'], resolve)
},
name:'home'
},
'/gameinfo/:gameid':{
component:function (resolve) {
require(['./components/game-info'], resolve)
},
name:'gameinfo'
},
'/rank/:gameid':{
component:function (resolve) {
require(['./components/rank'], resolve)
},
name:'rank'
},
'/gogame/:gameid':{
component:function (resolve) {
require(['./components/game-area'], resolve)
},
name:'gogame'
},
'/join':{
component:function (resolve) {
require(['./components/join'], resolve)
}
},
'/seerank/:userid':{
component:function (resolve) {
require(['./components/see-rank'], resolve)
},
name:'seerank'
}
})
router.beforeEach(function(transition){
transition.to.router.app.$el.classList.remove('fadein')
if(transition.from.name==='home'){
transition.to.router.app.savescrollhei=document.body.scrollTop;
}
if(transition.to.name==='home'){
setTimeout(function(){
window.scrollTo(0,transition.to.router.app.savescrollhei)
transition.to.router.app.$el.classList.add('fadein')
},500)
}else{
setTimeout(function(){
window.scrollTo(0,0)
transition.to.router.app.$el.classList.add('fadein')
},500)
}
transition.next()
})
router.afterEach(function(transition){
var title=transition.to.query.title?transition.to.query.title:'模拟炒股大赛'
if(transition.to.name==='rank'){title="排行榜"}
if(transition.to.name==='seerank'){title="交易记录"}
function hybridFun( str, obj, callback ){
// 页面已经加载完成
console.log(str)
console.log(obj)
if (window.WebViewJavascriptBridge) {
/*
* @param string 方法名
* @param object 参数
* @param callback 回调
*/
try{
//alert(1);
//alert(window.WebViewJavascriptBridge);
WebViewJavascriptBridge.callHandler( str, obj, callback )
} catch(e){
console.log(e);
}
// 还未加载完成
} else {
try{
document.addEventListener('WebViewJavascriptBridgeReady', function() {
//console.log(2);
//console.log(WebViewJavascriptBridge);
WebViewJavascriptBridge.callHandler( str, obj, callback )
}, false)
} catch(e){
console.log(e);
}
}
}
hybridFun('anchant',{title:title},function(){})
})
export default router
App
Hello
game-info
all-list
my-list
message
router
props
props
props
-
根组件(/src/App.vue)
<template>
<div class="appwrap" :scrollheiht='scrollval' >
<router-view :user='user' :show='show' :search='search' ></router-view>
</div>
</template>
<script>
export default {
data:function(){
return{
user:this.$route.query.user,
show:1,
search:false,
savescrollhei:0
}
},
ready:function(){
},
methods:{
},
events:{
showtab:function(v){
this.show=v
},
search:function(){
this.search=true;
},
goback:function(){
this.search=false
}
}
}
</script>
-
入口组件(/src/components/Hello.vue)
<template>
<div class="simstock">
<ul :class='{"searchhide":search}' @click='tabhander'>
<li><a tab-num='1' :class="{'custom-active-class':show==1}" >全部</a><i></i></li>
<li><a tab-num='-1' :class="{'custom-active-class':show==-1}">我的</a></li>
</ul>
<div class="contentwrap" v-if='!loading'>
<all-list :user='user' :all="all" :search='search' v-show='show==1' ></all-list>
<my-list :user='user' :my="my" :lxq='lxq' v-show='show==-1' ></my-list>
</div>
<p class="loading" v-else>
<img src="../../static/img/loading.gif" height="32" width="32" alt="">
</p>
</div>
</template>
<script>
import AllList from './all-list'
import MyList from './my-list'
export default {
props:['show','search'],
data () {
return {
user:this.$route.query.user,
loading:true,
all:[],
my:[],
lxq:[]
}
},
route:{
data:function(transition){
document.body.scrollTop=100;
this.$http.get('/yxt/trade/list?user='+this.user).then(({data})=>{
this.loading=false;
/*
解决webview title问题
*/
let title ='模拟炒股大赛';
// 原生触发
function changeTitle(title){
document.title = title;
var iframe = document.createElement('iframe');
iframe.style.visibility = 'hidden';
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.onload = function () {
setTimeout(function () {
document.body.removeChild(iframe);
}, 0);
};
document.body.appendChild(iframe);
}
changeTitle(title)
transition.next(
data.item
)
},(res)=>{
})
}
},
components: {
AllList,
MyList
},
methods:{
tabhander:function(e){
let tabNum=e.target.getAttribute('tab-num');
if(tabNum!=this.show){
this.show=this.show*-1
this.$dispatch('showtab',this.show)
}
}
},
events:{
}
}
</script>
-
全部组件(/src/components/all-list.vue)
<template lang="html">
<div class="alllist gamelist">
<div class="searcharr">
<input type="text" v-model='inputtext' :value="inputtext" name="" :class='{"search":search}' @focus='handlefocus' class="searchin">
<span :class='{"searchbtn":search&&inputtext.trim()!==""}' @click='gosearch'>搜索</span>
<span :class='{"searchbtn":search&&inputtext.trim()===""}' @click='goback'>取消</span>
</div>
<p :class='{"nosearch":nosearch}' class="nosearchtext">
没有找到你想要的比赛,请重新输入.
</p>
<ul >
<li v-for="item in all" :class="{'searchhide':item.matchname.search(searchtext)===-1}">
<div v-if="item.status=='2'||item.status=='6'||item.status=='8'">
<div class="topinfo">
<div class="detail">
<p class="name">
{{item.matchname}}
</p>
<p class="gametime">
<span>{{item.jsrq | toDate item.qsrq }}</span>
比赛时长(天)
</p><p class="gamerNum">
<span>{{item.count}}</span>
报名人数
</p>
</div>
<p class="circle joined" v-if='item.status==="4"'>
<span>排名<br>
{{item.pm}}</span>
</p>
<p class="circle gojoin " :class="{'gray':item.status==='2'||item.status==='8','org':item.status==='7'}" v-else >
<span :class="{'bigwidth':item.status==='8'}">
{{item.status | statusto | arrtoone 1}}
</span>
</p>
</div>
<div class="bottomstatus">
<img src="../../static/img/statu.png" alt="" />
比赛状态:{{item.status | statusto | arrtoone 0}}
</div>
</div>
<div v-link="{ name: 'gameinfo', params: { gameid: item.matchid},query:{title:item.matchname,userid:item.userid,num:item.count,pm:item.pm,status:item.status}}" v-else>
<div class="topinfo">
<div class="detail">
<p class="name">
{{item.matchname}}
</p>
<p class="gametime">
<span>{{item.jsrq | toDate item.qsrq }}</span>
比赛时长(天)
</p><p class="gamerNum">
<span>{{item.count}}</span>
报名人数
</p>
</div>
<p class="circle joined" v-if='item.status==="4"'>
<span>排名<br>
{{item.pm}}</span>
</p>
<p class="circle gojoin " :class="{'gray':item.status==='2'||item.status==='8','org':item.status==='7'}" v-else >
<span :class="{'bigwidth':item.status==='8'}">
{{item.status | statusto | arrtoone 1}}
</span>
</p>
</div>
<div class="bottomstatus">
<img src="../../static/img/statu.png" alt="" />
比赛状态:{{item.status | statusto | arrtoone 0}}
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props:{
all:Array,
search:Boolean
},
data: function () {
return {
searchtext:'',
inputtext:'搜索',
nosearch:false
}
},
route:{
},
computed: {
},
compiled:function(){
},
ready: function () {
if(this.search){
this.$el.getElementsByTagName('input')[0].focus()
}
},
attached: function () {},
methods: {
handlefocus:function(){
this.inputtext='';
this.nosearch=false;
this.$dispatch('search')
},
goback:function(){
this.searchtext='';
this.$dispatch('goback')
this.inputtext='搜索';
},
gosearch:function(){
this.searchtext=this.inputtext
let searchtext =this.searchtext;
this.nosearch=this.all.every(function(v){
return v.matchname.search(searchtext)==-1
})
}
},
components: {}
}
</script>
-
我的组件(/src/components/my-list.vue)
<template lang="html">
<div class="mylist gamelist">
<div class="userinfo">
<div class="userpic">
<img src='../../static/img/userpic.png' alt="" />
</div>
<p class="info">
<span>{{user}}</span>
<span v-link="{ name: 'gogame', params: { gameid:item.matchid },query:{matchid:item.matchid,title:item.matchname,userid:item.userid}}" v-for='item in lxq'>{{item.matchname}}>></span>
</p>
</div>
<p class="joinllistname">
我参加的比赛
</p>
<ul>
<li v-for='item in my'>
<div>
<a v-link="{ name: 'gogame', params: { gameid:item.matchid },query:{matchid:item.matchid,title:item.matchname,userid:item.userid}}" v-if='item.status!="7"&&item.status!="8"'>
<span>{{item.matchname}}</span>
<span>{{item.status | statusto | arrtoone 1}}</span>
</a>
<p v-else>
<span>{{item.matchname}}</span>
<span>{{item.status | statusto | arrtoone 1}}</span>
</p>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
props:{
user:String,
my:Array,
lxq:Array
},
data: function () {
return {
}
},
computed: {},
ready: function () {
},
attached: function () {},
methods: {},
components: {}
}
</script>
-
比赛信息组件(/src/components/game-info.vue)
<template lang="html">
<div class="gameinfo">
<msg :msg='msg' v-if='msg.showmsg' ></msg>
<p class="topinfo">
<span>{{$route.query.title}}<img src="../../static/img/images/log_03.png" alt="" /></span>
<span>{{$route.query.num}}</span>人已参加
</p>
<div class="gnamedetail">
<p >
比赛介绍
</p>
<p>
{{info.introduction}}
</p>
</div>
<div class="gamerule">
<p>
比赛规则
</p>
<p ><span>初始化:</span><span>未设置初始化</span></p>
<p ><span>起始资金:</span><span>{{info.funds}}元</span></p>
<p ><span>交易品种:</span><span>沪深</span></p>
</div>
<div class="gamedate">
<p>
比赛日程
</p>
<p >
<span>报名时间:</span><span> {{info.signstarttime}} 至 {{info.signendtime}}</span>
</p>
<p>
<span>比赛时间:</span><span>{{info.matchstarttime}} 至 {{info.matchendtime}}</span>
</p>
</div>
<p class="buttonlist">
<a v-link="{ name: 'rank', params: { gameid: $route.params.gameid},query:{pm:$route.query.pm,user:user,userid:$route.query.userid,title:$route.query.title}}">排行榜</a>
<a v-link="{ name: 'gogame', params: { gameid: $route.params.gameid},query:{userid:$route.query.userid,matchid:$route.params.gameid,title:$route.query.title}}" v-if='$route.query.status==="4"||$route.query.status==="5"'>进入比赛</a>
<i v-else>
<span v-if='$route.query.status==="7"'>审核中</span>
<a @click.stop='gojoin($route.params.gameid,$route.query.title)' class="gojoin" v-else>{{buttonText}}</a>
</i>
</p>
</div>
</template>
<script>
import msg from './message'
export default {
props:['user'],
data: function () {
return {
info:{},
buttonText:'立即参加',
msg:{
showmsg:'',
opmsg:'关闭'
}
}
},
route:{
data:function(transition){
let query="userid="+this.$route.query.userid+'&matchid='+this.$route.params.gameid
this.$http.get('/yxt/trade/matchinfo?'+query).then(({data})=>{
let title =this.$route.query.title? this.$route.query.title:'模拟炒股大赛';
// 原生触发
function changeTitle(title){
document.title = title;
var iframe = document.createElement('iframe');
iframe.style.visibility = 'hidden';
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.onload = function () {
setTimeout(function () {
document.body.removeChild(iframe);
}, 0);
};
document.body.appendChild(iframe);
}
changeTitle(title)
transition.next(
{info: data.item}
)
},(res)=>{
})
}
},
computed: {},
ready: function () {
this.$on('msgck',()=>{
this.msg.showmsg=''
})
},
methods: {
gojoin:function(gameid,title) {
this.$http.get('/yxt/trade/apply/',{matchid:gameid,user:this.user}).then((res)=>{
if(res.data.code=="0"){
this.$router.go({path:'/join', query: { matchid:gameid ,title:title,userid:res.data.item.userid}})
}else{
this.msg.showmsg=res.data.msg
if(res.data.msg.includes('加入成功')){
this.buttonText='审核中'
}
}
},(res)=>{
})
}
},
components: {
msg
}
}
</script>
-
消息组件(/src/components/message.vue)
<template lang="html">
<div class="wrap" >
<div class="message" @click='msgck'>
<p>{{msg.showmsg}}</p>
<p v-if="msg.opmsg">{{msg.opmsg}}</p>
</div>
</div>
</template>
<script>
export default {
props:['msg'],
data: function () {
return {
an:false
}
},
computed: {},
ready: function () {
},
attached: function () {
},
events:{
},
methods: {
msgck:function(){
this.$dispatch('msgck')
}
},
components: {}
}
</script>
遇到的问题及解决思路
-
使用rem单位在有margin的情况下宽度错乱
在使用rem时,盒模型的大小以设计稿测量数值除以设计稿总宽度的十分之一为其数值
.userinfo{
height: 145/64rem;
width: 625/64rem;
}
在实际使用时会出现小数点的问题。这可能导致在浏览器渲染时出现问题。在桌面浏览器上是没有问题的,App内嵌的webview中会出现宽度不对的问题,这时候使用百分比计算大小。
-
客户端获取网页title的问题
单页应用中,由于页面只加载一次,这导致客户端没有办法获取页面改变之后的页面title.
解决方法是在改变页面title后,模拟页面加载事件,触发客户端相应的事件监听:
// 原生触发
function changeTitle(title){
var body = document.getElementsByTagName('body')[0];
document.title = title;
var iframe = document.createElement("iframe");
iframe.setAttribute("src", "/favicon.ico");
iframe.addEventListener('load', function() {
setTimeout(function() {
iframe.removeEventListener('load');
document.body.removeChild(iframe);
}, 0);
});
document.body.appendChild(iframe);
}
document.getElementById('demo2').ontouchend = function(){
changeTitle('demo2 title');
}
ios下没有问题,安卓下会有问题。安卓使用js桥的方法去通知App修改title
-
保存浏览位置
vue-router 在H5模式下提供saveScrollPosition选项进行浏览位置保存,但是有时候不起作用。所以模拟下,在页面切换时保存上一个页面的scrollTop,再回退到上一页面时设置scrollTo滚动到之前的浏览位置。
router.beforeEach(function(transition){
transition.to.router.app.$el.classList.remove('fadein')
if(transition.from.name==='home'){
transition.to.router.app.savescrollhei=document.body.scrollTop;
}
if(transition.to.name==='home'){
setTimeout(function(){
window.scrollTo(0,transition.to.router.app.savescrollhei)
transition.to.router.app.$el.classList.add('fadein')
},500)
}else{
setTimeout(function(){
window.scrollTo(0,0)
transition.to.router.app.$el.classList.add('fadein')
},500)
}
transition.next()
})
谢谢
startvue
By Hucy Yang
startvue
初次使用vue 搭建移动端SPA的感想和收获
- 1,598