WEBAPP开发之《红包日历》ios版本——基于vue全家桶的webapp单页应用
白羽 2018-08-13 来源 :网络 阅读 778 评论 0

摘要:本文将带你了解WEBAPP开发之《红包日历》ios版本——基于vue全家桶的webapp单页应用,希望本文对大家学WEBAPP有所帮助。

        本文将带你了解WEBAPP开发之《红包日历》ios版本——基于vue全家桶的webapp单页应用,希望本文对大家学WEBAPP有所帮助。




项目介绍:制作一个网赚应用
平台:ios手机
技术选型:vue+vuex+vue-router+vue-resource+webpack+es6+sass+postcss
最终效果图:更多可到AppStore下载“悦动music”






首页





tipLayer

背景
毕业之后首个用新技术单独完成的项目,项目是从2015.10月开始的,当时vue还没有2.0。
产品需求
业务角度(不展开说,有兴趣私聊)
用户做任务(下载应用)-->检测有效性-->给分给用户
技术角度

可快速迭代
获取ios手机上的一些信息
用户体验友好

技术架构
出于以下的考量:
a.获取ios手机上的一些信息,只能通过ios客户端来实现,这是业务的重心
b.ios客户端需要上传到AppStore,每次迭代都需要至少2天的审核,这样对可快速迭代不利
c.因为a的一些实现跟AppStore的规则有打擦边球的嫌疑,有时候会突然下架
参考竞品,以及综合分析,然后我们定出了这样的架构:
WebApp+NativeApp+后端






架构

这里稍微说一下
黑线路径,要经过客户端,发挥客户端的优势,譬如说加密、一些客户端的功能(譬如说截图、第三方软件分享、登录)
蓝色路径,只要前端跟客户端拿到token,就可以直接跟后端通讯,免除每次都经过客户端

前端技术选型分析

客观角度
问题来了,作为多页WebApp,需要考虑的地方(更多点击这里):
i.状态管理
譬如说,多页面应用下,A页面跳去B页面,在B页面提交了数据,返回A的时候,我想利用B页面的数据,是不可以的,因为进行了刷新,又或者说这两个页面没有可以通讯的中介。其实我们可以用localStorage、url参数来作为中介,这两个页面之间的通讯还好说,但是如果是两个以上的,就很难维护了。
ii.丧失入口、路径控制权限
主观角度
2015年10月,vue还没出1.0,第一次接触mvvm框架,也听说过当时很热的Angular、React。当时是抱着越简单越好的心态去选,然后就无意中挑选了vue。关于mvvm的框架对比,可以看看这里。
而且也只有我一个人负责,所以更大胆地用新框架。途中经历过入职答辩,以及跟网友聊天。才发现自己在技术选型上是随意的,我问了网友一个关于vue-resource的跨域问题,网友问了我一个问题:为什么选择vue-resource,有什么特别之处吗?为什么不用普通的ajax?我这才从懵逼中醒悟过来,技术为需求而生。

单页面应用架构,有以下特点:
1)在一个页面下切换视图,而不需要重新加载整个网页,这样一来就减轻了加载资源的负载、缩短了用户的等待时间;
2)路由控制视图切换
3)组件化开发,利于分治、复用
4)MV*,免掉繁重的dom操作
5)方便共享数据
Vue是前端MVVM框架,它实现了组件化、模板渲染等功能
VueRouter可以控制路由,从而切换视图
VueResource封装了Promise的写法以及对RestfulAPI更友好
最后不谋而合,我们就选取了Vue全家桶来制作单页应用
当然单页应用也有缺点:
1)首次加载比较慢
2)对SEO不友好
3)浏览器本身的历史回退
JUSTDOIT√

构建项目
vue-cli:目录结构
webpackgulp:构建项目,压缩代码,自动化脚本,打包代码
npm:包管理
开发flow
gitflow
 搭建开发/测试环境:webpack-dev-serverhot-reloadwebpack.conf
webpack打包大小优化:codesplitting、压缩
webpack本地构建优化:把第三方库放在vendor或者externals等等
功能区分以及开发
utils
config
mixins
公用组件
view
布局
z轴上,采用weui的规范
区分公用组件和view进行布局
自适应布局flexible.js+rem+flex布局
-webkit-overflow-scrolling:touch造成的堆叠上下文
其他
官网上百度搜索,zhanzhang.baidu.com


vue的功能:(√表示项目中用到的)

数据驱动更新视图 √
试图切换&过渡效果 √
路由 √
组件之间的通讯※ √
状态管理 √
vdom
单元测试
后端渲染

制作的过程中,我觉得组件间通讯比较重要:
vue1.x:
方法①broadcast、dispatch(父子、兄弟组件通讯)vue2.x废弃该方法
方法②this.$root、this.$children(父子、兄弟组件通讯)
方法③prop、emit(父子组件通讯)vue1.xprop支持双向绑定
方法④this.$refs(父->子单向通讯)
vue2.x:
方法①eventbus(兄弟组件通讯)
方法②prop、emit(父子组件通讯)vue2.xprop不支持双向绑定

遇到的问题
1.跨域cookie共享
因为需要知道用于的登录状态,所以使用token来作为标示,前端传送的http头部如果带有token的cookie,后端检验token通过,就代表该用户有效且处于登录状态。但是跨域传输cookie需要配置一些东西。
如果不跨域,前端直接使用document.cookie,发送请求到后端,会自动带上cookie;
如果跨域,默认是不带cookie的,如果需要跨域带上cookie需要做以下步骤:
1)前端设置cookie的domain为后端的域名
document.cookie="key=value;domain=backend.website.com;path=/"

如果前端域名和后端域名的主域名是相同的,可以直接设置为主域名,譬如这个项目是前后端分离的,浏览器访问html文件,是m.hongbaorili.com,这个域名对应的是前端的文件,而后端的接口是ios.hongbaorili.com,他们有相同的部分hongbaorili.com,直接把cookie的domain设置为这个也可以。
document.cookie="token=xxx;domain=hongbaorili.com;path=/"

2)前端设置xhr.withCredentials=true
3)后端设置http返回头部
//设置允许跨域的域名,注意如果是跨域传送cookie,是不能设置为*的,必须指定域名
Access-Control-Allow-Origin://m.hongbaorili.com
//设置允许跨域共享cookie
Access-Control-Allow-Credentials:true

2.http的简单请求和非简单请求(preflight)
因为使用vue-resource来进行处理请求,其实它主要就是使用了promise包了一层ajax,当然还有设置了一些勾子让用户灵活设置,譬如我在这里提问的问题。
它还默认对post、get方法设置了一些方法和html头部,其中的post方法(vue-resourcev0.9.3)默认设置了HTTP头部Content-Type:"application/json;charset=utf-8。当我尝试使用vue-resource的方法的时候,会失败,抓包一看状态是403(methodnotallow),它发送了一个method为OPTION的包,我当时是想着有没有方法不走OPTION直接走post方法,就查到了相关的资料:
浏览器将CORS的请求分为:简单请求和非简单请求。
简单请求必须同时满足以下要求,否则为非简单请求:





来源:阮一峰博客

非简单请求:
请求方法是PUT或者DELETE
Content-Type:application/json
凡是非简单请求,在正式通讯之前,会发送一个OPTION方法的数据包,作为预检请求(preflight),询问后端当前请求是否在许可名单(Origin)、可以使用哪些http方法(Access-Control-Request-Method)、可以带上哪些头部信息字段(Access-Control-Request-Headers)。后端通过查询对应的Access-Control-Allow-Origin、Access-Control-Allow-Methods、Access-Control-Allow-Headers字段,如果通过就返回一个200,然后就进行数据通讯。如果不通过,返回的数据不包含跨域的信息头部,表示失败,这时候xhr的onerror就会响应。
所以找到了问题的关键是vue-resource默认的post方法使用了Content-Type:application/json触发了preflight。所以可以直接把默认的选项去掉,然后加上emulateJSON:true来表示application/x-www-form-urlencoded,然后就能触发简单请求。具体的解决步骤在这里。
以上两个问题详见:
//www.ruanyifeng.com/blog/2016/04/cors.html
3.组件:无限滚动
关键点:
1)判断滚动到底部,触发拉取新数据,添加新数据
判断滚动是否到底部有用到:滚上去的高度scrollTop+页面的高度clientHeight===网页的高度scrollEle.offsetHeight
2)零部件:
设置flag变量,防止滚动到底部发送多个请求;
设置page、size变量表示拉取的页数、数据条数;
3)优化点:
首次进入的时候,未获取到数据的时候,用变量loading来记录;
当加载完毕(条目<size||第一个的size===0),用变量nodata来记录。
使用-webkit-overflow-scrolling:touch在ios端滑动起来体验很好
可继续优化的点:
上拉加载更多,或者下拉加载更多,有多余的块显示
4.布局、组件与功能的考虑
首先我们把组件分为公用组件和私有组件,后来看到资料,发现私有组件都在view(视图)里面,所以应该是这样分类:components(组件)和view(视图)。
然后以App.vue为根组件,公共组件和routerview挂载在App.vue下,大概是这样的:





我的布局

其中遇到的问题:
1)一些可复用组件,譬如说confirm组件,它的模子就只有提示框的骨架,其中的内容需要用slot来写。它应该放在公共组件的位置,还是view里面?
当初我没有细想,就放在公共组件的位置,执行起来的时候,遇到超级不爽的地方:
①每个view都要跟公用组件通讯,传递提示框的自定义信息,包括插图地址、主标题、副标题、正文、提示;
②每个提示框的“取消事件”还好说,都是把confirm组件隐藏掉;但是”确定事件”就不是每个confirm组件是一样的,所以也需要动态绑定。
这种方案用以下两种方法实现组件间的通讯:
a.confirm组件作为全局组件,状态记录在vuex。这样对于①的操作就很简单了,传一个json过去就可以;但问题在于如何动态绑定“确定事件”,我的做法是在vuex添加一个变量yesCounter,每次confirm组件的“确定”按钮点击之后,yesCounter就+1,在view里面watch这个变量(yesCounter),方法写在view的methods里面,当监测到改变就触发事件。
b.confirm作为公共组件,挂在在App.vue下,指定组件名称confirm。在view里面,用this.$root.refs.confirm来调用里面的东西、以及赋值。
c.confirm组件作为view里面的组件,对于①的操作,很直观简单;对于②的操作用emit事件;而且这个方案的好处在于“按需加载”,因为有些view是不需要confirm组件的。
于是我采用方案c,但是呢,这又有一个问题,就是当confirm组件的出现的时候,我希望它把全屏遮住了,但是它又内嵌到view里面。《当时我没找到方法,就徘徊地用回方案a,但是的确太恶心了,就狠下心来把方案c产生的问题解决掉(这种习惯应该抛弃啊!)。当时想了三个方案:
i)app__header、app__content放在同一个wrapper里面,控制content的高度,超出的范围滚动条显示。这种ok






方案i

ii)app__header、app__content放在同一个wrapper里面,但是是使用flex布局的。这种方案肯定不可以,因为view怎么样都覆盖不了header的;
iii)app__header用fixed布局,app__content的高度是100%,header跟content的堆叠上下文是相同的,但是我需要把header置顶,所以直接把header的顺序放在下面。然后放在app__content的confirm组件设置z-index就可以了。





App.vue






confirm

无意中看到weui的布局,印证了当初自己的思考也比较合理





weui页面层级

5.抽象view的逻辑&&promise&&es6
由于每个view都有以下特点:
①每次加载的时候都会向后端ajax请求数据;
②通过设置route的data选项,如果①请求数据失败,视图就切换回去之前的;
③每次按刷新的时候,向后端ajax请求数据,更新data;
稍微分析一下,其实①②③的加载数据是可以复用的,但是②中,路由的data勾子要传入transition这个变量,用transition.next()和transition.abort()控制视图切换,是否但是在①③不需要。综合以上需求,就写了一个mixin,如下:
exportletrouterDataMixin={
route:{
data:function(transition){
varthat=this;
if(this.assist.token&&this.fetchOption){
newPromise(function(resolve,reject){
that.fetchData({resolve,reject})
})
.then(function(data){
transition.next();
})
.catch(function(error){
console.log(error);
transition.abort();
})
}else{
transition.next();
}
}
//waitForData:true
},
methods:{
fetchData:function(...rest){//用上es6的rest,很方便
if(!this.fetchOption){
returnfalse;
}

this.$http.get(
this.fetchOption.url,
{
params:this.fetchOption.params||{},
credentials:/hongbaorili/g.test(this.fetchOption.url)
}
).then(
function(response){
if(response.data.c===0){
if(this.fetchSuccess){
this.fetchSuccess(response);
}else{
this.userData=response.data.d
}
try{
rest[0].resolve();
}catch(e){}
}elseif(response.data.c===-10000){
}else{
if(this.fetchAbnormal){
this.fetchAbnormal(response);
}

try{
rest[0].reject(newError("fetchData:c!=0"))
}catch(e){}
}
this.endProgress();
},
function(response){
if(this.fetchFail){
this.fetchFail(response);
}else{
this.showToast();
}

try{
rest[0].reject(newError("fetchData:fail"));
}catch(e){}

this.endProgress();
});
}
}

6.BEM类命名方法
.component-name__component-part_component-status
eg:
.tab__tab-item_active
当然也可以灵活处理
.tab__tab-item.active
更多详见这里~
7.RestfulAPI
增删查改
postdelgetput
后端同事说项目小没必要这么复杂,就只做了get和post    


本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之WebApp频道!

本文由 @白羽 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程