概要
IDE: HBuilder 使用框架: JQuery 3.3.1 Vue2.x 描述: 利用 豆瓣 API 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 的夥伴們一些幫助.