Vue全家桶實現還原豆瓣電影wap版

douban-movie(豆瓣電影wap版)

用vue全家桶仿寫豆瓣電影wap版。javascript

最近在公司項目中嘗試使用vue,但奈何本身初學水平有限,上了vue沒有上vuex,開發過程特別難受。css

因而玩一玩本項目,算是對相關技術更加熟悉了。html

原計劃仿寫完全部頁面,礙於豆瓣的接口API有限,實現頁面也有限。vue

因爲公開的豆瓣接口具備訪問次數限制,克隆到本地體驗效果更加!java

web端訪問已設置寬度適配。webpack

進入GitHub查看本項目源碼git

歡迎issueprstar or follow!我將繼續開源更多有趣的項目!github

推薦一些以前寫的新手入門項目web

在線版

點擊進入

部分效果截圖

工具&技能

  • vue + vuex+ vue-router全家桶

  • webpack + webpack-dev-server + http-proxy-middleware進行本地開發環境http請求轉發,實現跨域請求

  • 線上使用expresshttp-proxy-middleware實現請求轉發

  • iView一款vue的組件庫

  • vue-lazyload實現圖片懶加載

  • rem + flex + grid實現移動端適配

  • http-proxy-middleware 一個http代理的中間件,進行http請求轉發,實現跨域請求

  • postman 接口測試工具

使用

git clone https://github.com/xingbofeng/douban-movie.git

cd douban-movie

npm install 

npm run dev

實現功能

首頁

  • [x] 影院熱映、即將上映、top250、北美票房榜

  • [x] 電影條目可橫向滾動

  • [x] 預覽電影評分

搜索頁

輸入搜索關鍵詞,回車鍵搜索,或者點擊搜索按鈕。

  • [x] 搜索功能

  • [x] 熱門搜索詞條的記錄

查看更多

  • [x] 預覽電影評分

  • [x] 滾動動態加載

  • [x] 數據緩存入vuex

電影詳情

  • [x] 電影評分

  • [x] 電影條目

  • [x] 演員列表

  • [x] 劇情簡介

  • [x] 數據緩存入vuex

搜索結果頁

  • [x] 翻頁功能

  • [x] 圖片懶加載

  • [x] 預覽電影條目

  • [x] 本地緩存瀏覽信息

目錄結構

|
|—— build 
|—— config
|—— server 服務端
| |—— app.js 服務端啓動入口文件
| |—— static 打包後的資源文件
| |__ index.html 網頁入口
|
|——src 資源文件
| |—— assets 組件靜態資源庫
| |—— components 組件庫
| |—— router 路由配置
| |—— store vuex狀態管理
| |—— App.vue douban-movieSPA
| |__ main.js douban-movieSPA入口
|
|__ static 靜態資源目錄

開發心得

如何緩存數據

這個問題在我以前的的項目總結已經總結過。

加入咱們有電影條目A、B、C三個電影條目詳情。進入A加載A,進入B加載B。此時也要把A緩存入vuex中。

能夠相似於下面的寫法。

{
  [`${A.id}`]: A,
  ...store.state
}

具體代碼可見/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();
}

翻頁加載

其實這個在以前的React項目中也有作過,設置一個currentPage的狀態,而後根據這個狀態來渲染頁面。

具體代碼可見/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);
  },

滾動加載

相似於瀑布流佈局的實現方式,當用戶滾動到距離頁面底部必定範圍的時候去請求後端接口。

具體代碼可見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;
  }
}

滾動節流

滾動節流主要做用是控制滾動事件的頻率,設置一個flag。未超過頻率則直接在函數中返回。

具體代碼可見src/containers/More.vue

scrolling() {
  // scrolling函數用於做函數節流
  if (this.scrollFlag) {
    return;
  }
  this.scrollFlag = true;
  setTimeout(() => {
    this.handleScroll();
    this.scrollFlag = false;
  }, 20);
}
  1. 404與加載頁面的實現

這裏主要是在vuex中設定兩個狀態。根據這兩個狀態返回不一樣的頁面。

具體代碼可見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>

在路由鉤子函數中改變狀態

以前在公司作React項目的時候運用了universal-router,當時咱們能夠在進入路由的時候dispatch一個action改變狀態,而且使用async/await函數實現異步。

貼一段以前的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));
  }
}

相似的,在vue中咱們也能夠這麼作!

具體代碼可見/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的封裝

其實我就是不想用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}`);

代理的配置

爲了解決瀏覽器跨域問題,須要在本地服務端配合實現請求轉發。

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);

移動端的適配

咱們使用rem做單位,本項目中標準爲1rem = 100px,適配750px設備。

瀏覽器執行下列代碼,改變根元素的font-size,作到移動端的適配。

<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);

文檔借鑑自個人同窗ShanaMaid

支持

BUG提交請發送郵箱: me@xingbofeng.com

歡迎issueprstar or follow!我將繼續開源更多有趣的項目!

你的支持將有助於項目維護以及提升用戶體驗,感謝各位的支持!

相關文章
相關標籤/搜索