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

news2024/11/25 18:37:40

演示

在这里插入图片描述

运行

基于本地代理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源码】
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习的十大核心算法

引言 深度学习是人工智能领域中最热门和最具影响力的分支之一。其核心在于通过构建复杂的神经网络模型&#xff0c;从大量的数据中自动学习并提取出有用的特征&#xff0c;从而实现各种高级的任务&#xff0c;如图像识别、自然语言处理等。本文将介绍深度学习中的十大核心算法…

Linux之进程(五)(进程控制)

目录 一、进程创建 1、fork函数创建进程 2、fork函数的返回值 3、fork常规用法 4、fork调用失败的原因 二、进程终止 1、进程终止的方式 2、进程退出码 3、进程的退出方法 三、进程等待 1、进程等待的必要性 2、wait函数 3、waitpid函数 四、进程程序替换 1、概念…

c++11特性:右值引用的作用以及使用

右值&#xff1a; C11 增加了一个新的类型&#xff0c;称为右值引用&#xff08; R-value reference&#xff09;&#xff0c;标记为 &&。在介绍右值引用类型之前先要了解什么是左值和右值&#xff1a; 1. lvalue 是locator value的缩写&#xff0c;rvalue 是 read v…

Swagger2接口测试文档

目录 一、Swagger简介 1.1 Swagger是什么&#xff1f; 1.2 为什么要用Swagger 1.3 Swagger注解 二、Spring集成Swagger 三、测试环境配置 一、Swagger简介 1.1 Swagger是什么&#xff1f; Swagger是一款RESTFUL接口的文档在线自动生成功能测试功能软件。Swagger是一个规…

(自适应手机版)英文外贸网站模板 - 带三级子目录

(自适应手机版)英文外贸网站模板 - 带三级子目录 PbootCMS内核开发的网站模板&#xff0c;该模板适用于外贸网站、英文网站类等企业&#xff0c;当然其他行业也可以做&#xff0c;只需要把文字图片换成其他行业的即可&#xff1b; 自适应手机版&#xff0c;同一个后台&#x…

数据安全扫描仪荣膺网络安全优秀创新成果大赛优胜奖 - 凸显多重优势

近日&#xff0c;由中国网络安全产业联盟&#xff08;CCIA&#xff09;主办、CCI数据安全工作委员会中国电子技术标准化研究院等单位承办的“2023年网络安全优秀创新成果大赛”获奖名单公布。天空卫士数据安全扫描仪&#xff08;DSS&#xff09;产品获得创新成果大赛优胜奖。 本…

从源码到实践:深入了解鸿鹄电子招投标系统与电子招投标

在数字化采购领域&#xff0c;企业需要一个高效、透明和规范的管理系统。通过采用Spring Cloud、Spring Boot2、Mybatis等先进技术&#xff0c;我们打造了全过程数字化采购管理平台。该平台具备内外协同的能力&#xff0c;通过待办消息、招标公告、中标公告和信息发布等功能模块…

JetBrains AI Assistant 最佳平替方案来了

先看看官方推荐 JetBrains IDE 中的 AI 助手 除了你自己&#xff0c;谁最了解你的项目&#xff1f;你的IDE&#xff01;这就是为什么 AI Assistant 可以如此具有上下文感知能力和帮助性的原因。 JetBrains AI 服务采用不同的大型语言模型 &#xff08;LLM&#xff09;&#xf…

ChatGPT助力Excel数据分析:让你的工作事半功倍!

文章目录 一、ChatGPT简介二、ChatGPT在Excel数据分析中的应用1. 数据清洗2. 数据处理3. 数据分析4. 数据可视化 三、如何使用ChatGPT进行Excel数据分析1. 安装ChatGPT插件2. 输入问题或命令3. 查看结果并调整参数4. 导出结果并分享四、总结与展望 《巧用ChatGPT高效搞定Excel数…

想将电脑屏幕共享到iPhone上,但电脑是Linux系统,可行吗?

常见Windows系统或macOS系统的电脑投屏到手机&#xff0c;难道Linux系统的电脑要投屏就是个难题吗&#xff1f; 想要将Linux系统投屏到iPhone、iPad、安卓设备、鸿蒙设备&#xff0c;其实你可以利用软件AirDroid Cast和Chrome浏览器&#xff01;连接同一网络就可以直接投屏。 第…

CSS自适应分辨率 amfe-flexible 和 postcss-pxtorem:大屏高宽自适应问题

前言 继上篇《CSS自适应分辨率 amfe-flexible 和 postcss-pxtorem》。 发现一个有趣的问题&#xff0c;文件 rem.js 中按照宽度设置自适应&#xff0c;适用于大多数页面&#xff0c;但当遇到大屏就不那么合适了。 问题 使用宽度&#xff0c;注意代码第2 和 4 行&#xff1a;…

Linux笔记---系统信息

&#x1f34e;个人博客&#xff1a;个人主页 &#x1f3c6;个人专栏&#xff1a;Linux学习 ⛳️ 功不唐捐&#xff0c;玉汝于成 目录 前言 命令 1. uname - 显示系统信息 2. hostname - 显示或设置系统主机名 3. top - 显示系统资源使用情况 4. df - 显示磁盘空间使用情…

go语言函数二、init函数定义与作用

go语言init函数定义与作用 在go语言中&#xff0c;每一个源文件都可以包含一个init函数&#xff0c;这个函数会在main函数执行前&#xff0c;被go运行框架调用&#xff0c;注意是在main函数执行前。 package main import ("fmt" )func init() {fmt.Println("i…

【C++高阶(八)】单例模式特殊类的设计

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C从入门到精通⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习C   &#x1f51d;&#x1f51d; 单例模式 1. 前言2. 设计一个不能被拷贝/继承的…

顶级加密混淆混淆工具测评:ipagurd

摘要 JavaScript代码安全需求日益增长&#xff0c;因此JavaScript混淆工具的使用变得广泛。本文将对专业、商业JavaScript混淆工具ipagurd进行全面评估&#xff0c;通过比较其功能、操作便捷性、免费试用、混淆效果等方面&#xff0c;帮助开发者选择适合自己项目需求的工具。 …

stm32学习总结:4、Proteus8+STM32CubeMX+MDK仿真串口收发

stm32学习总结&#xff1a;4、Proteus8STM32CubeMXMDK仿真串口收发 文章目录 stm32学习总结&#xff1a;4、Proteus8STM32CubeMXMDK仿真串口收发一、前言二、资料收集三、STM32CubeMX配置串口1、配置开启USART12、设置usart中断优先级3、配置外设独立生成.c和.h 四、MDK串口收发…

在windows上如何干净的卸载一个软件及其快捷方式

可以在控制面板里面卸载&#xff0c;可以卸载掉文件夹及其快捷方式&#xff0c;具体操作如下&#xff1a; 找到-》控制面板\程序\程序和功能 然后右键某一项&#xff0c;即可出现卸载功能项。 卸载不干净的方法&#xff1a;利用软件商店卸载&#xff0c;有可能卸载失败&#x…

Leetcode—238.除自身以外数组的乘积【中等】

2023每日刷题&#xff08;六十六&#xff09; Leetcode—238.除自身以外数组的乘积 前缀积后缀积实现代码 class Solution { public:vector<int> productExceptSelf(vector<int>& nums) {int n nums.size();vector<int> ans(n);int pre 1, suf 1;fo…

Shell 脚本应用(二)

实验案例&#xff1a;使用Shell脚本监控主机 实验环境 某公司随着业务的不断发展&#xff0c;所使用的Linux服务器也越来越多&#xff0c;管理员希望编写一个简单的性 能监控脚本&#xff0c;放到各服务器中&#xff0c;当监控指标出现异常时发送告警邮件。 需求描述 >编…

【技术】MySQL 日期时间操作

MySQL 日期时间操作 MySQL 系统时间MySQL 时间格式化MySQL 年月日时分秒周MySQL 日期计算时分秒时差日期差日期加减 MySQL 系统时间 now()&#xff1a;系统时间&#xff0c;年月日时分秒current_date&#xff1a;系统时间&#xff0c;年月日current_time&#xff1a;系统时间&…