资讯专栏INFORMATION COLUMN

Vue全家桶实现还原豆瓣电影wap版

Near_Li / 498人阅读

摘要:豆瓣电影版用全家桶仿写豆瓣电影版。原计划仿写完所有页面,碍于豆瓣的接口有限,实现页面也有限。由于公开的豆瓣接口具有访问次数限制,克隆到本地体验效果更加端访问已设置宽度适配。

douban-movie(豆瓣电影wap版)

用vue全家桶仿写豆瓣电影wap版。

最近在公司项目中尝试使用vue,但奈何自己初学水平有限,上了vue没有上vuex,开发过程特别难受。

于是玩一玩本项目,算是对相关技术更加熟悉了。

原计划仿写完所有页面,碍于豆瓣的接口API有限,实现页面也有限。

由于公开的豆瓣接口具有访问次数限制,克隆到本地体验效果更加!

web端访问已设置宽度适配。

进入GitHub查看本项目源码

欢迎issueprstar or follow!我将继续开源更多有趣的项目!

推荐一些之前写的新手入门项目

wx-audio(微信小程序:音乐播放器)

paintCanvas(vue实现的你画我猜)

css-grid-flex(关于css的grid布局和flex布局的入门心得)

在线版

点击进入

部分效果截图

工具&技能

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

404与加载页面的实现

这里主要是在vuex中设定两个状态。根据这两个状态返回不同的页面。

具体代码可见src/App.vue

在路由钩子函数中改变状态

之前在公司做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,做到移动端的适配。

(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!我将继续开源更多有趣的项目!

你的支持将有助于项目维护以及提高用户体验,感谢各位的支持!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/82652.html

相关文章

  • 前端--通用知識 - 收藏集 - 掘金

    摘要:闭包有多重前端知识点大百科全书前端掘金,,技巧使你的更加专业前端掘金一个帮你提升技巧的收藏集。 Vue全家桶实现还原豆瓣电影wap版 - 掘金用vue全家桶仿写豆瓣电影wap版。 最近在公司项目中尝试使用vue,但奈何自己初学水平有限,上了vue没有上vuex,开发过程特别难受。 于是玩一玩本项目,算是对相关技术更加熟悉了。 原计划仿写完所有页面,碍于豆瓣的接口API有限,实现页面也有...

    王笑朝 评论0 收藏0
  • Vue.js全家还原网易云音乐(Windows PC)

    摘要:项目地址由于网易云的限制,部分功能可能会失效,如有需要可以项目下来在本地运行,如果炸了,麻烦在评论中告知一下我因为做的是端所以请在电脑端访问源码地址项目预览评论和歌单详情都封了我的暂时无法使用这两个功能了项目描述前端部分实现了滑块弹出层歌词 项目地址 由于网易云的api限制,部分功能可能会失效,如有需要可以clone项目下来在本地运行,如果api炸了,麻烦在评论中告知一下我 因为做的...

    sf_wangchong 评论0 收藏0
  • Vue2.0开发仿豆瓣电影WebApp

    摘要:从之前黄轶老师的高仿外卖开始接触过这个滚动库,感觉体验感很好,用起来也比较顺手,所以在后来的项目联系中就一直在使用。 前言 虽然在此之前已经有类似的仿豆瓣电影的webapp,但或是开发的有些简洁功能不太完善,或是体验感觉得可以再完善下,所以自己摸索着对比豆瓣和豆瓣电影两款app做了一下,初步满足了自己的想法,经过几次完善基本不会出现bug,如果发现存在问题请告诉我修改,谢谢! 2017...

    gplane 评论0 收藏0
  • vue开发一个猫眼电影web app

    摘要:前言之前一直在学习原生的,但是无奈功力太浅,学了很长时候也只能写一些简单的小,知道遇见了,一切都变了,他的双向绑定和组件化思想让我迅速的爱上了他,可是光学不练是没有什么成就感的,想着豆瓣提供了免费的接口,不如就利用这个接口做一个电影网站,想 前言:之前一直在学习原生的javascript,但是无奈功力太浅,学了很长时候也只能写一些简单的小demo,知道遇见了vue,一切都变了,他的双向...

    habren 评论0 收藏0

发表评论

0条评论

Near_Li

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<