uniapp实现豆瓣电影微信小程序(附源码)

演示

在这里插入图片描述

运行

基于本地代理1

npm run dev:proxy1

基于本地代理2

npm run dev:proxy2

基于nginx 代理

npm run dev:nginx

目录结构

|__ douban                                    # 本地代理
    |__ app.js                                  # 方式 1
    |__ proxy.js                                # 方式 2
|__ src
    |__ App.vue
    |__ components                            # 组件
      |__ movie-item.vue                        # 电影列表项
      |__ movie-list.vue                        # 电影列表
    |__ main.js
    |__ pages
      |__ board                               # 榜单
        |__ index.vue
        |__ main.js
      |__ item                                # 电影详情
        |__ index.vue
        |__ main.js
      |__ list                                # 电影列表
        |__ index.vue
        |__ main.js
      |__ profile                             # 关于我
        |__ index.vue
        |__ main.js
      |__ search                              # 电影搜索
        |__ index.vue
        |__ main.js
      |__ splash                              # 启动页面
        |__ index.vue
        |__ main.js
    |__ store                                 # vuex
      |__ index.js                              # 全局
      |__ modules                               # 模块
        |__ item.js                               # 电影详情->对应 pages/item
      |__ mutations-type.js                     # mutations 常量
    |__ utils                                 # 工具
      |__ api.js                                # 豆瓣 api
      |__ index.js                              # 工具方法
      |__ request.js                            # flyio 配置
      |__ wechat.js                             # 微信小程序 api
      |__ wx.js                                 # wx
|__ static                                    # 静态资源
    |__ .gitkeep
    |__ images                                  # 图片
      |__ *.{png,jpg,gif,jpeg}

页面

tabBar包含榜单、搜索、我的

tabBar: {
      color: '#666666',
      selectedColor: '#000000',
      borderStyle: 'white',
      backgroundColor: '#f8f9fb',
      list: [
        {
          text: '榜单',
          pagePath: 'pages/board/main',
          iconPath: 'static/images/board.png',
          selectedIconPath: 'static/images/board-actived.png'
        },
        {
          text: '搜索',
          pagePath: 'pages/search/main',
          iconPath: 'static/images/search.png',
          selectedIconPath: 'static/images/search-actived.png'
        },
        {
          text: '我的',
          pagePath: 'pages/profile/main',
          iconPath: 'static/images/profile.png',
          selectedIconPath: 'static/images/profile-actived.png'
        }
      ]
    }

榜单

<template>
  <div class="md-board">
    <view class="md-board__slide">
      <swiper class="md-board__swiper" :indicator-dots="true" :autoplay="true" :interval="3000" :duration="1000">
        <swiper-item v-for="(movie, index) in movies" :key="index">
          <image class="md-board__slide-image" :src="movie.images.large" mode="aspectFill"/>
        </swiper-item>
      </swiper>
    </view>
    <view class="md-board__list" :scroll-y="true">
      <block v-for="(item, index) in boards" :key="item.key">
        <view class="md-board__item">
          <navigator :url="'../list/main?type=' + item.key + '&title=' + item.title" hover-class="none">
            <view class="md-board__title">
              <text class="md-board__title-text">{{ item.title }}</text>
              <image class="md-board__title-image" src="../../../static/images/arrowright.png" mode="aspectFill"/>
            </view>
          </navigator>
          <scroll-view class="md-board__content" :scroll-x="true">
            <view class="md-board__inner" v-if="item.key !== 'us_box'">
              <navigator v-for="(movie, i) in item.movies" :key="movie.id + index + i" :url="'../item/main?id=' + movie.id">
                <view class="md-board__movie">
                  <image class="md-board__movie-image" :src="movie.images.large" mode="aspectFill"/>
                  <text class="md-board__movie-text">{{ movie.title }}</text>
                </view>
              </navigator>
            </view>
            <view class="md-board__inner" v-else>
              <navigator v-for="(movie, i) in item.movies" :key="movie.rank + index + i" :url="'../item/main?id=' + movie.subject.id">
                <view class="md-borad__movie">
                  <image class="md-board__movie-image" :src="movie.subject.images.large" mode="aspectFill"/>
                  <text class="md-board__movie-text">{{ movie.subject.title }}</text>
                </view>
              </navigator>
            </view>
          </scroll-view>
        </view>
      </block>
    </view>
  </div>
</template>

<script>
import { mapState, mapActions } from 'vuex'

export default {
  computed: {
    ...mapState('board', {
      boards: state => state.boards,
      movies: state => state.movies
    })
  },

  methods: {
    ...mapActions('board', [
      'getBoards'
    ]),
    async getBoardData () {
      await this.getBoards()
    }
  },

  mounted () {
    this.getBoardData()
  }
}
</script>

<style lang="scss">
@include c('board') {

  @include e('swiper') {
    height: 480rpx;
  }

  @include e('slide-image') {
    height: 100%;
    width: 100%;
  }

  @include e('list') {
    box-sizing: border-box;
    background-color: #f8f9fb;
  }

  @include e('item') {
    display: flex;
    flex-direction: column;
    cursor: pointer;
    font-size: 20rpx;
    margin: 40rpx 0;
    padding: 20rpx;
    background-color: #fff;
  }

  @include e('title') {
    display: flex;
    margin-bottom: 10rpx;
    width: 100%;
  }

  @include e('title-text') {
    flex: 1;
  }

  @include e('title-image') {
    height: 20rpx;
    width: 20rpx;
  }

  @include e('content') {
    height: 300rpx;
  }

  @include e('inner') {
    display: flex;
    flex-direction: row;
    height: 300rpx;
    width: 900rpx;
  }

  @include e('movie') {
    display: flex;
    flex-direction: column;
    width: 180rpx;
    margin: 10rpx;
  }

  @include e('movie-image') {
    width: 180rpx;
    height: 250rpx;
  }

  @include e('movie-text') {
    text-align: center;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }
}

</style>

搜索

<template>
  <div class="md-search">
    <view class="md-search__header">
      <input class="md-search__input" v-model="q" :placeholder="subtitle" placeholder-class="md-search__placeholder" auto-focus @change="handleSearch"/>
    </view>
    <movie-list v-if="movies.length" :movies="movies" :has-more="hasMore"></movie-list>
  </div>
</template>

<script>
import { mapState, mapActions, mapMutations } from 'vuex'
import MovieList from '@/components/movie-list'
import { LIST_CLEAR_STATE } from '@/store/mutations-type'

export default {
  components: {
    'movie-list': MovieList
  },

  data () {
    return {
      q: '',
      subtitle: '请在此输入搜索内容'
    }
  },

  computed: {
    ...mapState('list', ['movies', 'hasMore', 'type'])
  },

  methods: {
    ...mapMutations('list', {
      clearState: LIST_CLEAR_STATE
    }),
    ...mapActions('list', [
      'getMovies'
    ]),
    async getSearchData () {
      await this.getMovies({type: 'search', search: this.q})
    },

    resetData () {
      this.clearState()
    },

    handleSearch () {
      this.resetData()
      this.getSearchData()
    }
  },

  onReachBottom () { // 上拉加载
    this.getSearchData()
  },

  onHide () { // 清空状态
    this.resetData()
  }
}
</script>

<style lang="scss">
@include c('search') {

  @include e('header') {
    display: flex;
    justify-content: center;
    border-bottom: 1rpx solid #ccc;
  }

  @include e('input') {
    width: 100%;
    padding: 20rpx 40rpx;
    color: #666;
    font-size: 38rpx;
    text-align: center;
  }

  @include e('placeholder') {
    color: #ccc;
    font-size: 38rpx;
    text-align: center;
  }
}

</style>

我的

<template>
  <div class="md-profile">
    <!-- <view class="md-profile__header">
      <text class="md-profile__title">{{ title }}</text>
    </view> -->
    <button open-type="getUserInfo">授权访问</button>
    <view class="md-profile__user" @click="getUserInfo">
      <image class="md-profile__user-avatar" :src="userInfo.avatarUrl" mode="aspectFit"/>
      <text class="md-profile__user-nickname">{{ userInfo.nickName }}</text>
      <text :hidden="!userInfo.city">{{ userInfo.city }}, {{ userInfo.province }}</text>
      <text :hidden="!userInfo.city"> Thanks~ </text>
    </view>
  </div>
</template>

<script>
import { login, getUserInfo } from '@/utils/wechat'
export default {
  data () {
    return {
      title: '关于',
      userInfo: {
        wechat: 'SG',
        nickName: 'https://github.com/mini-mpvue/mpvue-douban',
        avatarUrl: '/static/images/qrcode-sg.png'
      }
    }
  },

  methods: {
    async getUserInfo () {
      const data = await getUserInfo()
      this.userInfo = data.userInfo
    }
  },

  mounted () {
    login().then(res => {
      console.log(res)
    })
  }
}
</script>

<style lang="scss">
@include c('profile') {

  @include e('header') {
    display: flex;
    justify-content: center;
    border-bottom: 1rpx solid #ccc;
  }

  @include e('title') {
    padding: 40rpx;
    color: #999;
    font-size: 148rpx;
    text-align: center;
  }

  @include e('user') {
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  @include e('user-avatar') {
    width: 100%;
    height: 620rpx;
    margin: 40rpx;
  }

  @include e('user-nickname') {
    color: #aaa;
    font-size: 30rpx;
    margin-bottom: 30rpx;
  }
}

</style>

启动页

<template>
  <div class="md-splash">
    <swiper class="md-splash__swiper" indicator-dots>
      <swiper-item class="md-splash__item"  v-for="(item, index) in movies" :for-index="index" :key="item.id">
        <image :src="item.images.large" class="md-splash__image" mode="aspectFill"/>
        <button class="md-splash__start" @click="handleStart" v-if="index === movies.length - 1">立即体验</button>
      </swiper-item>
    </swiper>
  </div>
</template>

<script>
import { getStorage, setStorage } from '@/utils/wechat'
import { getBoardData } from '@/utils/api'
const LAST_SPLASH_DATA = 'LAST_SPLASH_DATA'

export default {
  data () {
    return {
      movies: []
    }
  },

  methods: {
    async getCache () {
      try {
        let res = await getStorage(LAST_SPLASH_DATA)
        const { movies, expires } = res.data
        // 有缓存,判断是否过期
        if (movies && expires > Date.now()) {
          return res.data
        }
        // 已经过期
        console.log('uncached')
        return null
      } catch (error) {
        return null
      }
    },

    handleStart () {
      // TODO: 访问历史的问题
      wx.switchTab({
        url: '../board/main'
      })
    },

    async getInitData () {
      let cache = await this.getCache()
      if (cache) {
        this.movies = cache.movies
        return
      }
      let data = await getBoardData({board: 'coming_soon', page: 1, count: 3})
      this.movies = data.subjects
      await setStorage(LAST_SPLASH_DATA, {
        movies: data.subjects,
        expires: Date.now() + 1 * 24 * 60 * 60 * 1000
      })
    }
  },

  mounted () {
    this.getInitData()
  }
}
</script>

<style lang="scss">
page {
  height: 100%;
}

@include c('splash') {
  height: 100%;

  @include e('swiper') {
    height: 100%;
  }

  @include e('item') {
    flex: 1;
  }

  @include e('image') {
    position: absolute;
    height: 100%;
    width: 100%;
    opacity: .9;
  }

  @include e('start') {
    position: absolute;
    bottom: 200rpx;
    left: 50%;
    width: 300rpx;
    margin-left: -150rpx;
    background-color: rgba(64, 88, 109, .4);
    color: #fff;
    border: 1rpx solid rgba(64, 88, 109, .8);
    border-radius: 200rpx;
    font-size: 40rpx;
  }
}

</style>

item详情

<template>
  <div class="md-item">
    <image v-if="movie.images" class="md-item__background" :src="movie.images.large" mode="aspectFill"/>
    <block v-if="movie.title">
      <view class="md-item__meta">
        <image class="md-item__poster" :src="movie.images.large" mode="aspectFit"/>
        <text class="md-item__title">{{ movie.title }}({{ movie.year }})</text>
        <text class="md-item__info">评分:{{ movie.rating.average }}</text>
        <text class="md-item__info">导演:<block v-for="director in movie.directors" :key="director.id"> {{ director.name }} </block></text>
        <text class="md-item__info">主演:<block v-for="cast in movie.casts" :key="cast.id"> {{ cast.name }} </block></text>
      </view>
      <view class="md-item__summary">
        <text class="md-item__label">摘要:</text>
        <text class="md-item__content">{{ movie.summary }}</text>
      </view>
    </block>
  </div>
</template>

<script>
import { mapState, mapActions, mapMutations } from 'vuex'
import { ITEM_CLEAR_MOVIE } from '@/store/mutations-type'
import wx from '@/utils/wx'

export default {
  data () {
    return {
      id: null
    }
  },

  computed: {
    ...mapState('item', {
      movie: state => state.movie
    })
  },

  methods: {
    ...mapActions('item', [
      'getMovie'
    ]),
    ...mapMutations('item', {
      clearMovie: ITEM_CLEAR_MOVIE
    }),
    async getMovieData (id) {
      await this.getMovie({ id })
      wx.setNavigationBarTitle({ title: this.movie.title + ' « 电影 « 豆瓣' })
    }
  },

  mounted () {
    const id = this.$root.$mp.query.id
    if (!id) {
      return wx.navigateBack()
    }
    this.id = id
    this.getMovieData(id)
  },

  onUnload () {
    this.clearMovie()
  }
}
</script>

<style lang="scss">
@include c('item') {

  @include e('background') {
    position: fixed;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    height: 100%;
    width: 100%;
    z-index: -1000;
    opacity: .1;
  }

  @include e('meta') {
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 50rpx 40rpx;
  }

  @include e('poster') {
    width: 100%;
    height: 800rpx;
    margin: 20rpx;
  }

  @include e('title') {
    font-style: 42rpx;
    color: #444;
  }

  @include e('info') {
    font-size: 24rpx;
    color: #888;
    margin-top: 20rpx;
    width: 80%;
  }

  @include e('summary') {
    width: 80%;
    margin: 30rpx auto;
  }

  @include e('label') {
    display: block;
    font-size: 30rpx;
    margin-bottom: 30rpx;
  }

  @include e('content') {
    color: #666;
    font-size: 22rpx;
    padding: 2em;
  }
}

</style>

构建

# 安装依赖
npm install

# 开发
npm run dev

# 基于本地代理1 开发
npm run dev:proxy1

# 基于本地代理2 开发
npm run dev:proxy2

# 基于nginx 代理开发
npm run dev:nginx

# 生产
npm run build

# 生产分析图表
npm run build --report

# 启动本地代理1
npm run proxy1

# 启动本地代理2
npm run proxy2

代理

Nginx 代理:

src/utils/request.js

request.config.baseURL = 'https://movie.douban.gusaifei.com/v2/movie'

随着应用一起启动

本地代理:

douban/app.js

npm run proxy1

douban/proxy.js

npm run proxy2

需要借助 npm scripts 启动,或者进入到 douban 目录,运行 node app.jsnode proxy.js

源码截图:

在这里插入图片描述

说明

如果本项目对您有帮助,欢迎 “点赞,关注” 支持一下 谢谢~

源码获取关注公众号「码农园区」,回复 【uniapp源码】
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/260841.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于vue-cli快速发布vue npm 包

一、编写组件 1. 初始化项目并运行 vue create vue-digital-countnpm run serve2. 组件封装 新建package文件夹 ​ 因为我们可能会封装多个组件&#xff0c;所以在src下面新建一个package文件夹用来存放所有需要上传的组件。 ​ 当然&#xff0c;如果只有一个组件&#xff…

SQL面试题挑战01:打折日期交叉问题

目录 问题&#xff1a;SQL解答&#xff1a;第一种方式&#xff1a;第二种方式&#xff1a; 问题&#xff1a; 如下为某平台的商品促销数据&#xff0c;字段含义分别为品牌名称、打折开始日期、打折结束日期&#xff0c;现在要计算每个品牌的打折销售天数&#xff08;注意其中的…

【yolov8系列】 yolov8 目标检测的模型剪枝

前言 最近在实现yolov8的剪枝&#xff0c;所以有找相关的工作作为参考&#xff0c;用以完成该项工作。 先细读了 Torch-Pruning&#xff0c;个人简单记录了下 【剪枝】torch-pruning的基本使用&#xff0c;有框架完成的对网络所有结构都自适应剪枝是最佳的&#xff0c;但这里没…

Redis 系统性总结看这一篇就够了

Redis&#xff08;Remote Dictionary Server )&#xff0c;即远程字典服务&#xff0c;是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并提供多种语言的API。 之前在公司一直忙于使用&#xff0c;很少做系统性的总结&a…

工具系列:PyCaret介绍_单变量时间序列代码示例

&#x1f44b; 工具系列&#xff1a;PyCaret介绍_单变量时间序列代码示例 PyCaret是一个开源的、低代码的Python机器学习库&#xff0c;可以自动化机器学习工作流程。它是一个端到端的机器学习和模型管理工具&#xff0c;可以大大加快实验周期&#xff0c;提高工作效率。 与其…

用C#也能做机器学习?

前言✨ 说到机器学习&#xff0c;大家可能都不陌生&#xff0c;但是用C#来做机器学习&#xff0c;可能很多人还第一次听说。其实在C#中基于ML.NET也是可以做机器学习的&#xff0c;这种方式比较适合.NET程序员在项目中集成机器学习模型&#xff0c;不太适合专门学习机器学习&a…

从Gitee克隆项目、启动方法

从gitee克隆VUE项目到本地后&#xff0c;不能直接运行&#xff0c;需要进行npm install安装node_modules文件夹里面的内容&#xff0c;因为在git上传的时候&#xff0c;一般都会过滤到node_modules中的依赖文件。 安装依赖以后&#xff0c;启动通过npm run serve启动项目出错。…

迪文屏开发保姆级教程——页面键盘

迪文屏页面键盘保姆级教程。 本篇文章主要介绍了在DGBUS平台上使用页面键盘的步骤。 迪文屏官方开发指南PDF&#xff1a;&#xff08;不方便下载的私聊我发给你&#xff09; https://download.csdn.net/download/qq_21370051/88647174?spm1001.2014.3001.5503https://downloa…

一篇文章带你搞定ARCHPR的下载和使用

除了bp这款爆破神器之外&#xff0c;另外还有一款ARCHPR的字典爆破神器&#xff0c;好处是很方便&#xff0c;而且爆破速度贼快 链接&#xff1a;https://pan.baidu.com/s/1-Ewx2JpZ-o5PunlfkRupYg 提取码&#xff1a;sg51 这里直接给大家安排了&#xff0c;自取就好 攻击的…

常用的电源芯片有哪些?怎么分类

科技的发展也带动了电源的发展&#xff0c;因此需要更多的电源管理芯片。说到电源管理芯片&#xff0c;作为工程师最熟悉的芯片之一。所谓电源管理芯片&#xff0c;就是负责电子设备系统中电能的转换、分配、检测等电能管理的芯片。主要负责识别CPU电源范围&#xff0c;产生相应…

【疑问】学前端是不是已经找不到工作了?深度回复“前端已死论”。

随着人工智能和低代码的崛起&#xff0c;“前端已死”的声音逐渐兴起。前端已死&#xff1f;尊嘟假嘟&#xff1f;快来发表你的看法吧&#xff01; 文章目录 一、为什么会出现“前端已死”的言论技术发展框架和工具的泛滥市场饱和全栈开发的兴起 二、你如何看待“前端已死”技术…

1.CentOS7网络配置

CentOS7网络配置 查看网络配置信息 ip addr 或者 ifconfig 修改网卡配置信息 vim /etc/sysconfig/network-scripts/ifcfg-ens192 设备类型&#xff1a;TYPEEthernet地址分配模式&#xff1a;BOOTPROTOstatic网卡名称&#xff1a;NAMEens192是否启动&#xff1a;ONBOOTye…

【halcon深度学习之那些封装好的库函数】create_dl_preprocess_param_from_model

函数简介 create_dl_preprocess_param_from_model 是一个用于创建深度学习模型预处理参数的程序。以下是该程序的详细介绍&#xff1a; 参数: DLModelHandle (输入): 用于预处理的深度学习模型的句柄。 NormalizationType (输入): 预处理时的归一化类型参数。可能的取值包括…

手机怎么设置每年公历或农历生日提醒?生日提醒设置小妙招

生日是一个人在一年中比较特殊的日子之一&#xff0c;人们通常希望能够在这一天得到亲朋好友的祝福和庆祝。然而&#xff0c;随着人们生活节奏的加快&#xff0c;很多人表示自己很容易忘记他人的生日&#xff0c;导致不能够及时送出祝福和礼物。如果经常忘记亲朋好友的生日&…

安防视频融合云平台/智慧监控平台EasyCVR如何添加验证码调用接口?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

Seata1.4.2分布式事务搭建部署【Spring cloud Alibaba】

包下载 https://github.com/apache/incubator-seata/releases下载并上传到服务器 //解压 tar -zxvf seata-server-1.4.2.tar.gz创建Seata的数据库及表&#xff0c;地址&#xff1a;https://github.com/seata/seata/tree/develop/script/server 根据数据类型复制相应的sql&…

工具系列:PyCaret介绍_多分类代码示例

&#x1f44b; 工具系列&#xff1a;PyCaret介绍_多分类代码示例 PyCaret 介绍 PyCaret是一个开源的、低代码的Python机器学习库&#xff0c;可以自动化机器学习工作流程。它是一个端到端的机器学习和模型管理工具&#xff0c;可以大大加快实验周期并提高生产效率。 与其他开…

验证码:防范官网恶意爬虫攻击,保障用户隐私安全

网站需要采取措施防止非法注册和登录&#xff0c;验证码是有效的防护措施之一。攻击者通常会使用自动化工具批量注册网站账号&#xff0c;以进行垃圾邮件发送、刷量等恶意活动。验证码可以有效阻止这些自动化工具&#xff0c;有效防止恶意程序或人员批量注册和登录网站。恶意程…

【bug日记】如何切换jdk版本,如何解决java和javac版本不一致

背景 今天在安装jenkins后&#xff0c;使用java运行war包的时候&#xff0c;提示jdk1.8版本太低&#xff0c;需要提高版本&#xff0c;所以就需要切换jdk版本 解决 在用户变量中&#xff0c;首先更改了JAVA_HOME的地址为17的目录&#xff0c;发现javac的版本改为17了&#x…

高防服务器防御靠谱吗?

​  随着互联网的普及和信息技术的不断发展&#xff0c;网络安全问题日益突出。高防服务器作为一种专业的网络安全设备&#xff0c;在防御网络攻击方面扮演着越来越重要的角色。然而&#xff0c;高防服务器是否靠谱&#xff0c;是否能够有效地防御各种网络攻击&#xff0c;一…