Vue Family Barrel Realizes Restoring wap Edition of Douban Film

Keywords: Javascript Vue Mobile React JSON

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

Click Enter

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

Search Page

Enter search keywords, return key search, or click on the magnifying glass Icon

See more

Film details

Search results page

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

  1. 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();
}
  1. 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);
  },
  1. 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;
  }
}
  1. 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);
}
  1. 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>
  1. 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();
}
  1. 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}`);
  1. 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);
  1. 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

Posted by LazyJones on Sat, 06 Jul 2019 12:30:13 -0700