我的第一个任务是做一个h5的拉新页面
设计稿
功能说明
在贝贝的贝壳任务新增"听儿歌赢奖励",期望能拿到更多拉新资源;
- 奖励1,播放儿歌赢奖励
- 每天抓取昨日播放量最高的前6个专辑;
- 用户默认访问时展示前3个,点击换一换更换后3个;
- 点击后直接进入播放器,播放内容是专辑的第一个内容;
- 点击页面上专辑位置,认为用户进行了1次播放,后端加1贝壳;
- 每个用户每天最多加3个;
- 奖励2,下载早教宝送30贝壳
- 点击按钮,跳转下载地址,安卓直接下载,iOS跳转到Appstore下载,下载成功+30贝壳;
- 奖励3,展示早教宝内的内容
- 展示内容,点击跳转下载地址。
如果这只是一个屋限制的单张页面,其实写起来很容易的,用我以前所学的只是,不到半天的时间就可以写出这样一张页面。但在公司就没有这么简单。一个公司代码量庞大,公司内部自己封装了许多经常用的函数,而且app用户量庞大,我们必须考虑到个方面的性能问题。
下面开始讲代码的问题
编辑器
公司开发大都用vscode编辑器,它和sublime很相似,但是它有更强大的功能
代码结构
公司内部封装好了,当需要新建一个页面时,运行
npm run newpage party/activity
会自动给你生成一个文件夹,文件夹下面包括四个文件
xtpl:是xtemplate的缩写,它是一种模板语言,和ejs类似。很多电商网站,如淘宝都使用这个模板语言。我们可以把列表渲染等拼接字符串丢到xtemplate里面,这样可以使我们的代码看起来简洁美观。
less:less的好用之处在于让我们的css和html一样具有层级关系,让我们的代码给具有可读性和可修改性。但要注意的是,层级嵌套不要太深,最多三级。因为浏览器读取的时候是从最里层向外找得。比如最里层是p,那就会找到页面中所有的p在找它的父级,很明显这样耗时。而我们又希望达到向html一样的层级关系,我们可以用&。下面是代码示例
.activity {
width: 100%;
letter-spacing: 2/@bs;
font-size: 26/@bs;
img {
display: block;
width: 100%;
}
&_music {
margin-top: -10/@bs;
padding: 6/@bs 40/@bs;
height: 345/@bs;
background-image: url(http://h0.hucdn.com/open/201804/5036d34bc97103eb_750x333.png);
background-size: 100% 100%;
}
}
最终“&_music”会被解析为“.activity_music”而不再是“.activity .music”
HTML
我们应该把html中需要和后端交互的一部分放在xtemplate中,然后通过js去将它添加到某个节点下
CSS
图片小缝隙问题
因为我写的是一张h5的拉新页面,页面的复用性不强,所以不需要点击的部分都是直接通过截图来实现的。
<div class="reward1">
<img src="http://h0.hucdn.com/open/201804/a092bae6fe513b30_750x335.png">
<div class="activity_music">
</div>
</div>
<div class="reward2">
<img src="http://h0.hucdn.com/open/201804/269bd63c6bf51ee2_749x87.jpg">
<img src="http://h0.hucdn.com/open/201804/76ad4d5de5f7aa8d_749x454.jpg" class="download" data-text=‘奖励二‘>
</div>
<div class="reward3">
<img src="http://h0.hucdn.com/open/201804/e832a8c05b217e81_750x102.jpg">
<img class="download" src="http://h0.hucdn.com/open/201804/7439e89b1ef75c84_749x910.jpg" data-text=‘奖励三‘>
</div>
<div class="reward4">
<img src="http://h0.hucdn.com/open/201804/704e66e11bd5863f_750x494.png">
</div>
</div>
这样会造成每个img后面都有一个小缝隙。这个小缝隙是怎么产生的呢,可以参看张鑫旭的CSS深入理解vertical-align和line-height的基友关系
简单的说,我把img放在div里面
<div class="mask">
<img src="http://h0.hucdn.com/open/201804/70999dd4e4598fe4_100x100.png">
</div>
在img后面会有一个很小的方块以这样的方式跟在它后面
要解决这个问题有这样几种方案
最简单的方法是将div和img之间的所有空格都去掉,向这样写
你可以这样理解,行内元素将自己包裹起来,同一行内允许其他元素存在,所以我们在书写html时div和img之间留了空隙,网页上自然就也有缝隙。但是这种方法不推荐使用,因为这样书写不美观,而且有的编辑器会自动为你添加缩进。<div class="mask"><img src="http://h0.hucdn.com/open/201804/70999dd4e4598fe4_100x100.png"></div>
- 将img的display设为block。因为这个原因是由img的display默认为inline-block造成的,所以我们将img的dispaly设为block让它变成块级元素就可以了。块级元素自占整个一行,将img设为block就没有元素可以和它共享这一行的位置了。
- 将父级的font-size设置为0。一个空格相当与一个字,将font-size设为0了之后,这个字将不再占用空间。但使用这个方法的时候要注意,一定要在它的子集里面重设font-size,不然真正的文字也变得看不见了。
- 给img设置float:left。这样做也不好,还需要去清除浮动。
将vel-align设为bottom。这样改变img与小方块的对其方式其实小白块还是存在,只是在纵向就看不出来了。
div大小固定问题
我在做一个列表展示的时候,使用了flex让它自适应为3块,于是就没有给它设置宽高了。但是我没有考虑到的是有可能它的标题很长,这样的话它就会自动将文字放到下面一排,这样效果就很丑了。所以我需要设置它的宽高,并且overflow:hidden这样即使它溢出也不会改变样式了。
box-sizing
我在写一段代码的时候,背景是切的一个图片,而我在width等于图片大小后,有对div设置了padding。这样我其实拉伸了背景图片,虽然效果影响不大,但是代码还是欠严谨。毕竟这几px的距离,可能是UI构思了很久的结果
js
整体思路
- 先引入一些插件
- 新建一个xtemplate模板
- Promise.polyfill()如果游览器不支持,帮你自动修补
- 拿数据
- 定义一些共用的函数
- 建一个class
- constructor中放一些共用的变量和需要执行的函数
- 定义一些只有在class里面在会用到的函数
- 启动页面
```
‘use strict‘
import ‘./index.less‘
/eslint-disable no-unused-vars/
import sentry from ‘@base/sentry/zaojiaobao_m‘
import Promise from ‘es6-promise‘
import Xtemplate from ‘xtemplate/lib/runtime‘
import tmpl from ‘./index.xtpl‘
import { ajax, authenticationHandle } from ‘commons/js/tools.js‘
import Hybrid from ‘@base/simple_hybrid‘
import { sendLog, pageStart } from ‘libs/log/log2.js‘
const tpl = new Xtemplate(tmpl)
Promise.polyfill()
const getData = () => {
return ajax({
method: ‘GET‘,
query: {
method: ‘beibei.education.weal.reward.list‘
}
})
}
class Page {
constructor() {
Hybrid(‘customNavBarRightBtn‘).customNavBarRightBtn({
//默认隐藏右上角分享按钮
hidden: true
})
}
}
pageStart()
new Page()
### 需要编写的函数
#### 将音乐渲染到页面中
因为音乐有多首,所以我采用xtemplate去渲染它们
这里需要注意的是,我用到了data-id和data-target这些属性,这是为了在js中我更方便去取到需要的数据
js部分
getData()
.then(res => {
this.res = res
this.albums = [res.albums.slice(0, 3), res.albums.slice(3, 6)]
this.res.albums = this.albums[0]
this.$music.html(tpl.render(this.res))
})
.catch(err => {
popup.note(err.message || err.err_msg || JSON.stringify(err))
})
这段函数放到class的constructor里面。这里用到了promise的异步请求,将请求成功的操作放在then里面,报错放在catch里面。catch几种报错是为了记录报错的设备。
因为一共有六条数据,但是页面只显示三条数据,如果我将六条数据全交给模板,会造成样式混乱。而且因为换一换需要的是前三条数据和后三条数据的切换,所以我将数据切好放在一个数组中,为换一换做好准备
#### 点击换一换切换音乐
let _this = this
this.$music.on(‘click‘, ‘.changMusic‘, function() {
sendLog({
type: ‘click‘,
params: {
block_name: ‘点击换一换‘
}
})
if (_this.res.albums === _this.albums[0]) {
_this.res.albums = _this.albums[1]
} else {
_this.res.albums = _this.albums[0]
}
_this.$music.html(tpl.render(_this.res))
})
这里的思路是判断当前状态,如果是前三个就切换到后三个
sendLog是一个已经封装好了的函数,通过这个记录用户的动作,方便运营对数据进行分析。
#### 点击奖励二或三跳到下载页面
this.$activity.on(‘click‘, ‘.download‘, function() {
let text = $(this).attr(‘data-text‘)
sendLog({
type: ‘click‘,
params: {
block_name: ‘点击领取聪明豆下载‘,
text: decodeURIComponent(text)
}
})
authenticationHandle(() => {
ajax({
method: ‘POST‘,
query: {
method: ‘beibei.education.download.reward.check‘
}
})
download()
})
})
```
这一部分的逻辑就是点击的时候跳到下载,但是要登录之后才可以下载。
authenticationHandle()是封装好的登录函数,download是封装好的下载函数。
谈一谈this
见我的另一篇文章this的指向和用法
完整代码https://github.com/hyz1997/beibei-edu-activty