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!
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!
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!
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
- hot-movie, comming-soon, top250 and us-box.
- horizontal scrolling.
- preview the score of the movie.
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.
- preview the score of the movie.
- loading when you scroll.
- save the data you accessd to
vuex
.
- score of the movie.
- information of the movie.
- actors list.
- the plot.
- save the data you accessd to
vuex
.
- turn the page.
- lazyload images.
- preview the information of the movie.
- cache your browsing.
|
|—— 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
{
[`${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();
}
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);
},
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;
}
}
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);
}
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>
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();
}
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}`);
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);
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!
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 !