Awesome
douban-movie
It is the douban-movie Application built with webpack + vue + vuex + vue-router + iView.
because the limit of open API is 40 times / minute.I recommended you clone this project to you own local environment.
Enter GitHub to see code!
Thanks for you support, waiting for your issue
, pr
, star
or follow
!I will release more interesting project in the future!
Online
Or you can clone this project to you own local environment, then enjoy this project online:
git clone https://github.com/xingbofeng/douban-movie.git
cd douban-movie
yarn install
yarn run server
Then open your browser, and go to http://localhost:3000/ to enjoy it!
Development
git clone https://github.com/xingbofeng/douban-movie.git
cd douban-movie
yarn install
yarn run dev
Then open your browser, and go to http://localhost:8080/ to enjoy it!
Preview
Technology stack
vue
+vuex
+vue-router
vue based projectwebpack
+webpack-dev-server
+http-proxy-middleware
dev environment we use webpack-dev-server and http-proxy-middleware.express
+http-proxy-middleware
online we use express and http-proxy-middlewareiView
UI components libraryvue-lazyload
help us lazyload imagesrem
+flex
+grid
responsive layout in mobileyarn
package manager.postman
test our interface
Functions
src/containers/Home.vue
- hot-movie, comming-soon, top250 and us-box.
- horizontal scrolling.
- preview the score of the movie.
src/containers/Search.vue
You should input some word which is you want to search, then press Enter
and begin to search, or you can click the button of search.
- search.
- save the hot search record.
src/containers/More.vue
- preview the score of the movie.
- loading when you scroll.
- save the data you accessd to
vuex
.
src/containers/MovieDetail.vue
- score of the movie.
- information of the movie.
- actors list.
- the plot.
- save the data you accessd to
vuex
.
src/containers/Tah.vue
- turn the page.
- lazyload images.
- preview the information of the movie.
- cache your browsing.
Directory
|
|—— build
|—— config
|—— server
| |—— index.js : the entry of the server.
| |—— static/ : static files after packaging.
| |__ index.html : the entry of this application.
|
|——src : dev resources.
| |—— assets : images
| |—— components/
| | |____ Common/ : reusable components
| | |____ ... : other components of the own page.
| |
| |—— router/
| | |____ index.js : the entry of router.
| | |____ server.js : export ajax function.
| | |____ serverConfig.js : export the server detail.
| | |____ routes/ : every page's router, changing the state of `vuex` at its lifecycle function.
| |
| |—— store : vuex
| |—— App.vue : douban-movieSPA
| |__ main.js : the entry of douban-movieSPA
|
|__ static : static files
What did I learnt in this project?
How to save data in vuex?
{
[`${A.id}`]: A,
...store.state
}
see the codes of /src/router/routes
.
beforeEnter: (to, before, next) => {
const currentMovieId = to.params.currentMovieId;
if (store.state.moviedetail.currentMovie[`${currentMovieId}`]) {
store.commit(types.LOADING_FLAG, false);
next();
return;
}
store.commit(types.LOADING_FLAG, true);
currentMovie(currentMovieId).then((currentMovieDetail) => {
// 成功则commit后台接口的数据,并把NET_ERROR的数据置空,并把加载中的状态置为false。
const id = currentMovieDetail.id;
store.commit(types.CURRENT_MOVIE, {
[`${id}`]: currentMovieDetail,
...store.state.moviedetail.currentMovie,
});
store.commit(types.LOADING_FLAG, false);
store.commit(types.NET_STATUS, '');
document.title = `${currentMovieDetail.title} - 电影 - 豆瓣`;
}).catch((error) => {
document.title = '出错啦 Oops… - 豆瓣';
store.commit(types.NET_STATUS, error);
store.commit(types.LOADING_FLAG, false);
});
next();
}
How to turn the page and load?
We set a state named currentPage
,everytime we change this state, the page will rerender.
see the codes of /src/containers/Tag.vue
.
computed: {
...mapState({
tagData(state) {
return state.tag.tagData[`${this.$route.params.currentTagId}`];
},
}),
subjects() {
return this.tagData.subjects.slice(
(this.currentPage - 1) * 10,
this.currentPage * 10,
);
},
},
methods: {
...mapActions(['getMoreTagData']),
changePage(flag) {
const currentTagId = this.$route.params.currentTagId;
const { start, count } = this.tagData;
// 第一页不能往前翻页,最后一页不能往后翻页。
if ((this.currentPage === 1 && flag === 'reduce') ||
(this.currentPage === Math.ceil(this.tagData.total / 10) && flag === 'add')
) {
return;
}
if (flag === 'add') {
this.currentPage = this.currentPage + 1;
// 每次请求十条数据
this.getMoreTagData({
tag: currentTagId,
count: 10,
start: count + start,
});
// 需要使用localStorge保存当前的页码信息,再次进入可以有这个页码信息。
const doubanMovieCurrentPage = JSON.parse(window.localStorage.doubanMovieCurrentPage);
window.localStorage.doubanMovieCurrentPage = JSON.stringify({
...doubanMovieCurrentPage,
[`${currentTagId}`]: this.currentPage,
});
} else {
this.currentPage = this.currentPage - 1;
}
window.scrollTo(0, 0);
},
How to scroll and load?
like the waterfall layout,when user scroll to some location , we request the data form back-end.
see the codes of src/containers/More.vue
。
handleScroll() {
// 函数的作用是滚动加载电影详情信息
// 判断是否为请求后台中的状态,如果是则返回
const { start, count, total } = this.currentSeeMore;
if (!this.requestFlag) {
return;
}
// 不同浏览器top展现会不一致
let top = window.document.documentElement.scrollTop;
if (top === 0) {
top = document.body.scrollTop;
}
const clientHeight = document.getElementById('app').clientHeight;
const innerHeight = window.innerHeight;
const proportion = top / (clientHeight - innerHeight);
// 但如果已把所有数据加载完毕了,则不请求
if (proportion > 0.6 && (start + count) < total) {
this.getMoreData({
count,
start: start + count,
title: this.$route.params.title,
});
this.requestFlag = false;
}
}
How to throttle when user scroll?
To implementation throttle of scrolling, we set a flag
.when flag === true
, we return the scroll function.
see the codes of src/containers/More.vue
.
scrolling() {
// scrolling函数用于作函数节流
if (this.scrollFlag) {
return;
}
this.scrollFlag = true;
setTimeout(() => {
this.handleScroll();
this.scrollFlag = false;
}, 20);
}
404 and loading
set two states in vuex
.
see the codes of src/App.vue
<template>
<div id="app">
<net-error
v-if="netStatus"
:netStatus="netStatus"
/>
<loading
v-else-if="!netStatus && loadingFlag"
/>
<router-view v-else></router-view>
</div>
</template>
How to change the asynchronous state?
We often use universal-router in React
project.in that case, we can dispatch
an action
to change the state of redux
, when we entry/change the router, and we use async/await fuction.
Like this code of React:
async action({ store, params }) {
// 判断store里的id和当前id是否一致,若一致,则不请求后台
console.log("chapter")
const chapterInfos = store.getState().home.chapterInfos;
if (Object.keys(chapterInfos).length === 0 ||
chapterInfos.subject.id !== parseInt(params.chapter, 10)) {
await store.dispatch(chapter(params.chapter));
}
}
And in this project, I had imitated it!
see the codes of /src/router/routes
beforeEnter: (to, before, next) => {
document.title = '电影 - 豆瓣';
if (Object.keys(store.state.home.homeData).length !== 0) {
store.commit(types.LOADING_FLAG, false);
next();
return;
}
store.commit(types.LOADING_FLAG, true);
Promise.all([
hotMovie(8, 0),
commingSoon(8, 0),
top250(8, 0),
usBox(8, 0),
]).then((homeData) => {
// 成功则commit后台接口的数据,并把NET_ERROR的数据置空,并把加载中的状态置为false。
store.commit(types.HOME_DATA, homeData);
store.commit(types.LOADING_FLAG, false);
store.commit(types.NET_STATUS, '');
}).catch((error) => {
document.title = '出错啦 Oops… - 豆瓣';
store.commit(types.NET_STATUS, error);
store.commit(types.LOADING_FLAG, false);
});
next();
}
Ajax
import serverConfig from './serverConfig';
const Ajax = url => new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.send(null);
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText));
} else {
reject(`错误: ${xhr.status}`);
}
}
};
});
// 影院热映
export const hotMovie = (count, start) =>
Ajax(`${serverConfig}/v2/movie/in_theaters?count=${count}&start=${start}`);
// 即将上映
export const commingSoon = (count, start) =>
Ajax(`${serverConfig}/v2/movie/coming_soon?count=${count}&start=${start}`);
// top250
export const top250 = (count, start) =>
Ajax(`${serverConfig}/v2/movie/top250?count=${count}&start=${start}`);
// 北美票房榜
export const usBox = (count, start) =>
Ajax(`${serverConfig}/v2/movie/us_box?count=${count}&start=${start}`);
// 当前电影详情信息
export const currentMovie = currentMovieId =>
Ajax(`${serverConfig}/v2/movie/subject/${currentMovieId}`);
// 当前标签详情信息
export const getTagData = (tag, count, start) =>
Ajax(`${serverConfig}/v2/movie/search?tag=${tag}&count=${count}&start=${start}`);
How to set proxy?
In dev environment we use webpack-dev-server
and http-proxy-middleware
, and online we use express
and http-proxy-middleware
.
proxyTable: {
'/v2': {
target: 'http://api.douban.com',
changeOrigin: true,
pathRewrite: {
'^/v2': '/v2'
}
}
},
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();
app.use('/static', express.static('static'));
app.use('/v2', proxy({
target: 'http://api.douban.com',
changeOrigin: true,
headers: {
Referer: 'http://api.douban.com'
}
}
));
app.get('/*', function (req, res) {
res.sendFile(__dirname + '/index.html');
});
app.listen(3000);
Responsive layout in mobile
Use rem
! In this project 1rem = 100px
!
The browser run the codes following,change the font-size
of the document.
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">
(function (doc, win) {
var docEl = doc.documentElement,
resizeEvt = 'orientationchange' in window ? 'orientationchange' : 'resize',
recalc = function () {
var clientWidth = docEl.clientWidth > 750 ? 360 : docEl.clientWidth ;
if (!clientWidth) return;
docEl.style.fontSize = clientWidth / 750 * 100 + 'px';
};
if (!doc.addEventListener) return;
doc.addEventListener('DOMContentLoaded', recalc, false);
if (docEl.clientWidth > 750) return;
win.addEventListener(resizeEvt, recalc, false);
})(document, window);
Chinese document referencing my friend ShanaMaid!
Support
Thanks for you support,being glad for your star
, pr
, follow
and issue
.
When you see bugs.You can mail to me! me@xingbofeng.com !