豆瓣列表 + 詳情 VUE 2.X 初使用 H5 手機預覽

雲舒s發表於2018-12-27

概要

IDE: HBuilder 使用框架: JQuery 3.3.1 Vue2.x 描述: 利用 豆瓣 API github.com/Pingsh/-Api 建立豆瓣電影列表和電影簡介

豆瓣電影列表

  • 很簡單的兩個頁面, 豆瓣電影列表及電影詳情, 不存在什麼 UI 審美

實現和效果圖

列表的 css 基於 mui.css , 主要程式碼和效果圖如下 :

HBuilder 有

<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 }}&nbsp;</span>
	<span id="genres" class="movie-detail-text" v-for="gen in subject.genres">/{{gen}}</span>
	<div id="original-title" class="movie-detail-text">原名: &nbsp;<span>{{subject.original_title}}</span></div>
	<div id="mainland-pubdate" class="movie-detail-text">上映時間: &nbsp;<span>{{subject.mainland_pubdate}}</span></div>
	<div id="durations" class="movie-detail-text" v-for="duration in subject.durations">片長: &nbsp;{{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;">&nbsp;</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-ifng-show.

內聯樣式和css中的樣式 資料請求成功後, 需要設定 icon 是否顯示, 如果將關鍵樣式寫在 css 中, 而非標籤內的 style 內, 修改無效. 這裡用了個小技巧, 在簡介上方加上一行空白行, 獲取簡介的文字高度, 超過 4 行則顯示 icon; 未超過則不顯示.

總結

這個 demo 是 2018年初寫的, 當時剛接觸 H5 和 Vue, 寫法一點也不 JavaScript, 有很多缺陷(輕點拍磚 哈哈哈哈), 年終翻出來看了看, 希望能給初次接觸 H5 的夥伴們一些幫助.

相關文章