概要
IDE: HBuilder
使用框架: JQuery 3.3.1 Vue2.x
描述: 利用 豆瓣 API https://github.com/Pingsh/-Api 创建豆瓣电影列表和电影简介
豆瓣电影列表
- 很简单的两个页面, 豆瓣电影列表及电影详情, 不存在什么 UI 审美
实现和效果图
列表的 css 基于 mui.css , 主要代码和效果图如下 :
<div id="pull-subject-list" class="mui-content mui-scroll-wrapper">
<div class="mui-scroll">
<div class="mui-input-row mui-search">
<input v-model="search" debounce="500" id="search" type="search" class="mui-input-speech mui-input-clear" placeholder="搜索" value=""></div>
<!--数据列表-->
<ul class="mui-table-view mui-table-view-chevron" v-for="subject in subjects" track-by="id" @tap="show_detail(subject)">
<li class="mui-table-view-cell mui-media">
<a style="padding:10px 35px 10px 10px;">
<img class="img_show mui-pull-left" :src="subject.images.small" />
<div class="mui-media-body" style="padding-left:10px;margin-top: 3px;">
{{subject.title}}
</div>
<h5 style="float:right;" class="mui-ellipsis">{{subject.mainland_pubdate}}</h5>
</a>
</li>
</ul>
</div>
</div>
var base_url = "https://api.douban.com/v2/movie/"
var pi = startIndex;
var startIndex = 0;
var movieCount = 10;
mui.init({
pullRefresh: {
container: '#pull-subject-list',
down: {
style: 'circle',
callback: pulldownRefresh
},
up: {
auto: true,
contentrefresh: '正在加载...',
callback: pullupRefresh
}
}
});
jQuery.ajax({
url: base_url + '/in_theaters',
data: {
apikey: '0b2bdeda43b5688921839c8ecb20399b',
city: '%E5%8C%97%E4%BA%AC',
start: startIndex,
count: movieCount,
client: 'somemessage',
udid: 'ddddddd'
},
dataType: 'jsonp',
success: function(data) {
if(data && data.subjects.length > 0) {
mui('#pull-subject-list').pullRefresh().endPullupToRefresh(data.subjects.length < movieCount); //参数为true代表没有更多数据了。
if(pi == 0) {
vm.subjects = data.subjects;
} else {
vm.subjects = vm.subjects.concat(data.subjects);
}
mui('#pull-subject-list').pullRefresh().endPulldownToRefresh(true);
startIndex += movieCount;
}
}
});
var vm = new Vue({
el: '#pull-subject-list',
data: {
search: '',
subjects: []
},
methods: {
show_detail: function(subject) {
console.log("id: " + subject.id);
localStorage.setItem("id", subject.id);
mui.openWindow({
url: "movie_detail.html",
show: {
autoShow: true, //页面loaded事件发生后自动显示,默认为true
aniShow: "slide-in-right", //页面显示动画,默认为”slide-in-right“;
duration: 500 //页面动画持续时间,Android平台默认100毫秒,iOS平台默认200毫秒;
}
});
},
getData: function() {
setTimeout(getSubjects(), 1500);
},
created: function createData() {
this.getData();
}
});
vm.$watch('search', function() {
startIndex = 0;
mui('#pull-subject-list').pullRefresh().refresh(true);
getSubjects();
}, {
deep: true
});
敲黑板的知识点
v-model
是 Vue 中的语法糖, 用作双向绑定, 达到的效果是: 输入变化即搜索. 豆瓣没有公开搜索的 API ,此处只做了样式.
v-for
是 Vue 中的语法糖, 常见于列表. 新建 vue 对象之后, 在 data:中对页面的数据进行编辑, 实体的属性名称可以不写, 联网之后直接使用. 在 css 中使用时, 有两种方式: 一种是采用上面的 {{subject.title}} , 写在标签中; 另一种是使用 v-bind 或 v-model . 实际项目中, 更建议第二种方式, 加载缓慢时, 不会出现 {{}} 占位的情况. 建议实际敲一遍, 看看效果.
track-by
配合 v-for 使用, 用来复用dom和原来的作用域. 这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出. Vue 2.x 的推荐是这样 :
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>
官方说法: 建议尽可能在使用 v-for 时提供 key,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。
jsonp
本来是准备用原生的方式写联网请求, 一直请求不到数据, 以前用 Android 没这毛病呀… 查找了很久, 发现是跨域问题, 也算是豆瓣的保护机制… 故, 引入 JQuery. dataType: 设置为 jsonp: , 即可成功访问.
电影详情
- 详情的预期需求是, 如果文字超过 4 行, 不显示右下角的 icon; 如果超过 4 行, 显示 icon , 且默认收起; 点击 icon, 改变 icon 方向, 展示所有文本.
实现和效果图
<header class="mui-bar mui-bar-nav " style="background: #45B1F7;height: 50px;">
<a class="mui-action-back mui-icon mui-icon-back mui-pull-left"></a>
<h5 class="mui-title" style="color: white;font-size: 1.1em; margin-top: 3px;">电影详情</h5>
</header>
<div id="movie-detail" class="mui-content mui-fullscreen" style="background: #ECF7FE;background-size: 100%,100%;">
<div style="width: 60%; margin: 20px;">
<div class="movie-title" id="title">{{subject.title}}</div>
<span id="year" class="movie-detail-text">{{ subject.year }} </span>
<span id="genres" class="movie-detail-text" v-for="gen in subject.genres">/{{gen}}</span>
<div id="original-title" class="movie-detail-text">原名: <span>{{subject.original_title}}</span></div>
<div id="mainland-pubdate" class="movie-detail-text">上映时间: <span>{{subject.mainland_pubdate}}</span></div>
<div id="durations" class="movie-detail-text" v-for="duration in subject.durations">片长: {{duration}}</div>
</div>
<div class="movie-mark">
<div class="movie-detail-text">豆瓣评分</div>
<div style="font-size: 30px; line-height: 100%;" id="average">{{subject.rating.average}}</div>
<div class="movie-detail-text" id="ratings-count">{{subject.ratings_count}}人</div>
</div>
<div class="movie-dash"></div>
<div style="padding: 30px 20px; background: #ECF7FE;">
<div class="movie-detail-text" style="font-size: 18px; margin-bottom: 20px;">简介</div>
<div ref="singleLine" style="line-height: 1.3em;"> </div>
<div id="summary-part1" :class="{foldSummary:fold, unfoldSummry:!fold}" ref="movieSummary">{{subject.summary}}</div>
<a id="summary-part2" class="mui-icon mui-pull-right mui-icon-arrowup" v-on:click="handleFold" ref="foldIcon" v-show="showIcon"></a>
</div>
</div>
var sub = new Vue({
el: '#movie-detail',
data: {
subject: {
genres: [],
rating: [average],
durations: []
},
fold: false,
showIcon: true,
},
methods: {
handleFold: function() {
this.fold = !this.fold;
},
getData: function() {
setTimeout(getMovieDetail(), 100);
}
},
created: function create() {
this.getData();
},
watch: {
fold: function(val) {
var icon = this.$refs.foldIcon;
if(this.fold) {
icon.setAttribute('class', 'mui-icon mui-pull-right mui-icon-arrowup');
} else {
icon.setAttribute('class', 'mui-icon mui-pull-right mui-icon-arrowdown');
}
}
// 图标是否显示不能写在watch, 事件没有被主动触发
}
/*,
//不能写在 mounted, 数据暂未获取
mounted: function() {
this.$nextTick(function() {
var content = this.$refs.movieSummary;
var test = this.$refs.test;
console.log(content.innerHTML);
for(var i = 0, len = content.style.length; i < len; i++) {
var prop = content.style[i]
console.log(prop); //遍历样式width;height;background-color;
console.log(content.style.getPropertyValue(prop)); //取得样式里面的值;
}
if(content.style.display == 'block') {
this.showIcon = false;
} else {
this.showIcon = true;
}
})
}*/
});
function getMovieDetail() {
jQuery.ajax({
url: movieurl,
data: {
apikey: '0b2bdeda43b5688921839c8ecb20399b',
city: '%E5%8C%97%E4%BA%AC',
client: 'somemessage',
udid: 'ddddddd'
},
dataType: 'jsonp',
success: function(data) {
if(data) {
sub.subject = data;
//sub.subject.summary = "测试短评";
}
},
complete: function() {
sub.$nextTick(function() {
var singleHeight = parseInt(this.$refs.singleLine.offsetHeight);
var height = parseInt(this.$refs.movieSummary.offsetHeight);
//此时只能获取行内样式, 不能获取 css 中 class 的样式
//console.log(jQuery("#summary-part1").height());
if(Math.round(height / singleHeight) > 4) {
this.showIcon = true;
this.fold = true;
} else {
this.showIcon = false;
this.fold = false;
}
})
}
});
}
敲黑板的知识点 +2
Vue的生命周期
写出列表页面之后, 开始思考, 既然 Android 中有生命周期, 很多操作都和生命周期息息相关, JS 中应该也是同理(PS: 来自移动开发人员的直觉). 在此不一一赘述, 感兴趣可以查看这篇文章: Vue 生命周期. 在没有理解生命周期之前, 曾经尝试在mounted 中利用 $nextTick 对样式进行修改, 理所当然失败啦.
ref
Vue 中直接操作 DOM 元素,用它进行注册, 更常用的方式和组件相关. 此处只用它获取元素, 如果愿意, 这份代码中用 id 或者 name 等其他方式替代也没有问题.
:class
缩写, 相当于
<div v-bind:class="{ foldSummary:fold }"></div>
类似的还有
<a v-on:click="doSomething">...</a>
<!-- 缩写 -->
<a @click="doSomething">...</a>
v-show
顾名思义, 布局是否显示, 类似的还有 v-if. 两者的区别是:
在v-show中,元素是一直存在的, 当v-show为false时, 元素display:none只是隐藏了而已.
在v-if中, 元素不是一直存在, 判断是否加载固定的内容, 如果为真, 则加载; 为假时, 则不加载. 控制元素插进来或者删除, 而不是隐藏.
v-show 在加载时, 比 v-if 消耗高; v-if 的切换消耗比 v- show 高.
结合这些分析, icon 是否显示, 只在初始化数据时进行了判断, v-if 更加合理. 但是, 我写的是 v-show , 这是个错误示范.
前端的其他框架中也有类似处理, 比如 AngularJS 中的 ng-if和ng-show.
内联样式和css中的样式
数据请求成功后, 需要设置 icon 是否显示, 如果将关键样式写在 css 中, 而非标签内的 style 内, 修改无效. 这里用了个小技巧, 在简介上方加上一行空白行, 获取简介的文本高度, 超过 4 行则显示 icon; 未超过则不显示.
总结
这个 demo 是 2018年初写的, 当时刚接触 H5 和 Vue, 写法一点也不 JavaScript, 有很多缺陷(轻点拍砖 哈哈哈哈), 年终翻出来看了看, 希望能给初次接触 H5 的伙伴们一些帮助.