Douban-movie
Write the wap version of Douban Film with the vue family barrel.
Recently, we tried to use Vue in our company's projects, but we have limited initial learning level. The development process is particularly painful because we did not use Vue on vue.
So play this project, is more familiar with the relevant technology.
The original plan to copy all pages, due to the limited interface API Douban, page implementation is limited.
Because of openness Douban joint With limited access times, cloning to local experience is more effective!
Width adaptation has been set for web-side access.
Enter GitHub to view Source code for this project
Welcome to issue, pr, star or follow! I will continue to open source more interesting projects!
Recommend some novice introductory projects written before
Online Edition
Partial effect screenshot
Tools & Skills
vue + vuex+ vue-router family bucket
webpack + webpack-dev-server + http-proxy-middleware forwards HTTP requests to achieve cross-domain requests
iView a vue component library
Lazy loading of images by vue-lazyload
Re + flex + grid for mobile adaptation
http-proxy-middleware An http proxy middleware that forwards http requests to achieve cross-domain requests
Posman Interface Testing Tool
Use
https://github.com/xingbofeng/douban-movie.git cd douban-movie npm install npm run dev
Implementing Functions
home page
[x] Cinema hit, upcoming, top 250, North American box office
[x] Film entries can be scrolled horizontally
[x] Preview Film Score
Search Page
Enter search keywords, return key search, or click on the magnifying glass Icon
[x] search function
Recording of Top Search Items
See more
[x] Preview Film Score
[x] rolling dynamic loading
[x] data cached into vuex
Film details
[x] Film Score
[x] Film entry
[x] list of actors
A Brief Introduction to [x]
[x] data cached into vuex
Search results page
[x] page turning function
[x] Lazy loading of pictures
[x] Preview Film Items
[x] Local cache browsing information
directory structure
| |- build |- config |—— server server server ||—— app.js Server Start Entry File ||—— static packaged resource file ||_ index.html Web Page Entry | |—— src resource file ||—— assets Component Static Resource Library ||—— components Components Library ||—— router routing configuration ||—— store vuex state management | |- App.vue douban-movieSPA ||_ main.js douban-movieSPA entry | |_ static static resource directory
Development Experience
How to cache data
This problem has been summarized in my previous project summary.
Join us with details of three movie entries: A, B and C. Enter A to load A and B to load B. At this point, we also need to cache A into vuex.
It can be similar to the following.
{ [`${A.id}`]: A, ...store.state }
The specific code can be seen in the following related files: / 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) => { // Successfully, the data of the commit back-end interface is emptied and the status in loading is set to 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} - Film - Bean paste`; }).catch((error) => { document.title = 'Error Oops... - Bean paste'; store.commit(types.NET_STATUS, error); store.commit(types.LOADING_FLAG, false); }); next(); }
Page Flipping Loading
Actually, this was done in the previous React project. Set up a current Page status, and then render the page according to that status.
Specific code can be seen in / 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; // The first page cannot be turned forward, and the last page cannot be turned back. 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; // Ten data requests at a time this.getMoreTagData({ tag: currentTagId, count: 10, start: count + start, }); // You need to use localStorge to save the current page number information, and re-enter to have this page number information. 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); },
Rolling loading
Similar to the implementation of waterfall flow layout, when the user scrolls to a certain distance from the bottom of the page to request the back-end interface.
The specific code can be seen in src/containers/More.vue.
handleScroll() { // The function loads movie details scrolling // Determines whether it is a state in the request background, and if so returns const { start, count, total } = this.currentSeeMore; if (!this.requestFlag) { return; } // Different browsers have different top presentations 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); // However, if all data has been loaded, no request is made. if (proportion > 0.6 && (start + count) < total) { this.getMoreData({ count, start: start + count, title: this.$route.params.title, }); this.requestFlag = false; } }
Rolling throttling
The main function of rolling throttling is to control the frequency of rolling events and set a flag. If the frequency is not exceeded, it is returned directly in the function.
Specific code can be seen in src/containers/More.vue
scrolling() { // scrolling function for function throttling if (this.scrollFlag) { return; } this.scrollFlag = true; setTimeout(() => { this.handleScroll(); this.scrollFlag = false; }, 20); }
404 and Implementation of Loading Page
Here we mainly set two states in vuex. Return different pages based on these two states.
Specific code can be seen in 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>
Change state in routing hook function
Previously, it was used when the company was working on React projects. universal-router At that time, we can dispatch an action to change state when we enter the routing, and use async/await function to achieve asynchrony.
Paste a previous React code:
async action({ store, params }) { // Determine if the ID in the store is the same as the current id, and if it is the same, do not request the background 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)); } }
Similarly, we can do this in vue!
Specific code can be seen under / src/router/routes
beforeEnter: (to, before, next) => { document.title = 'Film - Bean paste'; 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) => { // Successfully, the data of the commit back-end interface is emptied and the status in loading is set to false. store.commit(types.HOME_DATA, homeData); store.commit(types.LOADING_FLAG, false); store.commit(types.NET_STATUS, ''); }).catch((error) => { document.title = 'Error Oops... - Bean paste'; store.commit(types.NET_STATUS, error); store.commit(types.LOADING_FLAG, false); }); next(); }
Ajax Encapsulation
Actually, I just don't want to use the relevant libraries operated by 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(`error: ${xhr.status}`); } } }; }); // Cinema Hot Show export const hotMovie = (count, start) => Ajax(`${serverConfig}/v2/movie/in_theaters?count=${count}&start=${start}`); // Upcoming release 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}`); // North American box office export const usBox = (count, start) => Ajax(`${serverConfig}/v2/movie/us_box?count=${count}&start=${start}`); // Current Film Details export const currentMovie = currentMovieId => Ajax(`${serverConfig}/v2/movie/subject/${currentMovieId}`); // Current Label Details export const getTagData = (tag, count, start) => Ajax(`${serverConfig}/v2/movie/search?tag=${tag}&count=${count}&start=${start}`);
Configuration of agents
In order to solve the cross-domain problem of browsers, it is necessary to cooperate with local servers to realize request forwarding.
proxyTable: { '/v2': { target: 'http://api.douban.com', changeOrigin: true, pathRewrite: { '^/v2': '/v2' } } },
In a real environment, server-side configuration
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);
Mobile adaptation
We use rem as our unit. The standard in this project is 1 REM = 100 px, adapted to 750 PX devices.
The browser executes the following code, changes the font-size of the root element, and achieves the adaptation of the mobile end.
<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 > 640) return; win.addEventListener(resizeEvt, recalc, false); })(document, window);
Documents Learn from Self-Student ShanaMaid