乐于分享
好东西不私藏

8、uni-app项目——“短视频”微信小程序

8、uni-app项目——“短视频”微信小程序

1. uni-app框架概述

    uni-app是使用Vue.js开发的一个前端框架,基于该框架可以很方便地进行多端项目开发,只需要编写一套代码,即可生成为多个平台项目。uni-app框架支持的平台包括iOS、Android、响应式Web和各种主流的小程序等。

uni-app框架开发项目的4点优势

1、平台能力不受限。使用uni-app框架进行项目开发能做到“一套代码,多端发行”。

2、性能体验优秀。加载新页面速度快,可以自动更新数据。

3、基于通用技术栈,学习成本低。uni-app框架基于前端开发中比较通用的Vue.js技术栈,使开发者不必增加学习成本。

4、具有开放的生态且组件更丰富。例如,支持通过npm安装第三方包,支持微信小程序自定义组件及SDK,支持微信生态的各种SDK。

参考学习官网:

https://uniapp.dcloud.net.cn/quickstart.html

2. HBuilder X开发工具

下载:https://dcloud.io/hbuilderx.html

地址:https://ext.dcloud.net.cn/plugin?name=compile-node-sass

3. 创建uni-app项目

4. uni-app项目的目录结构

5. 将uni-app项目运行至微信小程序

6. uni-app项目的全局配置文件

7.案例准备事项

1.项目准备

2. 实现添加待办事项的功能

1.初始化数据(index.vue)

<script>export default {   data() {    return {       showList: [],       value: '',       status: ''	}   },   methods: {	}}</script>

2.编写页面实现(index.vue)

<template><view><viewclass="top"><inputtype="text"placeholder="请输入待办事项"v-model="value"><viewclass="createButton" @click="handleClick">添加</view></view></view></template>
<script>export default {data() {return {showList: [],value: '',status: ''			}		},methods: {handleClick() {this.showList.push({value: this.value,status: 'todo', // 表示待办事项id: new Date().getTime()				})console.log(this.showList)this.value = ''			},		}	}</script><style>/* 顶部区域 */.top {  width: 100%;  height: 80rpx;  display: flex;  align-items: center;  justify-content: space-between;  padding: 15rpx 0 15rpx 0;  border-bottom: 1px dashed lightgray;}.top input {  width: 500rpx;  margin-left: 20rpx;}.top .createButton {  width: 100rpx;  height: 60rpx;  display: flex;  align-items: center;  justify-content: center;  border: 1rpx solid gray;  border-radius: 50rpx;  margin-right: 20rpx;}</style>

3. 实现列表区域

待办事项:

<template><view><viewclass="top"><inputtype="text"placeholder="请输入待办事项"v-model="value"><viewclass="createButton" @click="handleClick">添加</view></view><viewclass="bottom"><viewclass="title">待办事项</view><viewv-for="(item, index) in showList":key="item.id"><viewv-if="item.status === 'todo'"class="doing"><checkboxv-on:click="doingHandle(item.id, index)" />					{{ item.value }}</view></view></view></view></template>
<script>export default {data() {return {showList: [],value: '',status: ''			}		},methods: {handleClick() {this.showList.push({value: this.value,status: 'todo', // 表示待办事项id: new Date().getTime()				})console.log(this.showList)this.value = ''			},doingHandle(id, index) {const item = this.showList.find((item) => item.id === id)				item.status = 'finished'			},		}	}</script>

已办事项

<template><view><viewclass="top"><inputtype="text"placeholder="请输入待办事项"v-model="value"><viewclass="createButton" @click="handleClick">添加</view></view><viewclass="bottom"><viewclass="title">待办事项</view><viewv-for="(item, index) in showList":key="item.id"><viewv-if="item.status === 'todo'"class="doing"><checkboxv-on:click="doingHandle(item.id, index)" />					{{ item.value }}</view></view><viewclass="title">已办事项</view><viewv-for="(item, index) in showList":key="item.id"><viewv-if="item.status === 'finished'"class="done"><checkboxv-on:click="doneHandle(item.id, index)"checked="true" /><viewclass="conent">						{{ item.value }}</view></view></view></view></view></template>
<script>export default {data() {return {showList: [],value: '',status: ''			}		},methods: {handleClick() {this.showList.push({value: this.value,status: 'todo', // 表示待办事项id: new Date().getTime()				})console.log(this.showList)this.value = ''			},doingHandle(id, index) {const item = this.showList.find((item) => item.id === id)				item.status = 'finished'			},doneHandle(id, index) {const item = this.showList.find((item) => item.id === id)				item.status = 'todo'			}		}	}</script>
<style>/* 顶部区域 */.top {width: 100%;height: 80rpx;display: flex;align-items: center;justify-content: space-between;padding: 15rpx 0 15rpx 0;border-bottom: 1px dashed lightgray;	}.top input {width: 500rpx;margin-left: 20rpx;	}.top .createButton {width: 100rpx;height: 60rpx;display: flex;align-items: center;justify-content: center;border: 1rpx solid gray;border-radius: 50rpx;margin-right: 20rpx;	}/* 底部区域 */.bottom {margin: 30rpx;	}.bottom .title {margin-top: 30rpx;font-weight: bold;	}.bottom .done,.bottom .doing {width: 100%;height: 70rpx;background-color: #EDEDED;display: flex;align-items: center;justify-content: flex-start;margin-top: 20rpx;	}.conent {text-decoration: line-through;	}	checkbox {margin: 0 15rpx 0 15rpx;	}</style>

8.uni-app项目——“短视频”微信小程序

1.项目内容

2.项目分析

3.项目初始化

4.公共头部区域

4.1. 实现公共头部区域的页面结构

MyTitle.vue

<template>  <viewclass="my-title">    <!-- logo -->    <navigatorclass="logo">      <imageclass="logo-img"src="../../static/logo.png" />    </navigator>    <!-- 搜索框 -->    <viewclass="search-icon">      <imagesrc="../../static/search.jpg" />    </view>    <!-- 用户头像 -->    <viewclass="user-icon">      <imagesrc="../../static/user.jpg" />    </view>    <!-- 下载APP按钮 -->    <viewclass="down-app">      下载APP    </view>  </view></template>

4.2实现公共头部区域的页面样式

MyTitle.vue

<stylelang="scss">.my-title {  display: flex;  justify-content: center;  align-items: center;  padding: 10rpx;  background-color: #fff;  height: 70rpx;  .logo {    flex: 7;    .logo-img {      width: 180rpx;      height: 60rpx;    }  }  .search-icon {    flex: 1;    display: flex;    justify-content: center;    align-items: center;    image {      width: 60rpx;      height: 44rpx;    }  }  .user-icon {    flex: 2;    display: flex;    justify-content: center;    align-items: center;    image {      width: 54rpx;      height: 60rpx;    }  }  .down-app {    flex: 3;    font-size: 30rpx;    display: flex;    justify-content: center;    align-items: center;    background-color: #87CEEB;    color: #fff;    border-radius: 10rpx;    padding: 10rpx;  }}</style>

4.3. 在页面中显示公共头部区域

运行看效果。

5.导航栏区域

5.1. 加载导航栏数据

index.vue

<script>  import config from '../../common/config.js'  export default {    data() {      return {        navList: [],        currentIndexNav: 0,      }    },    onLoad() {      this.getNavList()    },    methods: {      getNavList() {        uni.request({          url: config.url + '/navList',          success: res => {            this.navList = res.data.data.navList            console.log(this.navList) // 输出到控制台          }        })      },    }  }</script>

5.2. 实现导航栏区域的页面结构

index.vue

<template>  <view>    <MyTitle></MyTitle>    <viewclass="nav_wrap">      <scroll-viewclass="nav"scroll-x>        <view @click="activeNav($event,index)":class="['nav_item',index===currentIndexNav?'active':'']"          :data-index="index" v-for="(item,index) in navList" :key="item.id">          {{item.text}}        </view>      </scroll-view>    </view></template>

5.3. 实现导航栏区域的页面样式

index.vue

<stylelang="scss">  .nav {    white-space: nowrap;    padding: 5rpx 0;    .nav_item {      padding: 20rpx 45rpx;      font-size: 30rpx;      display: inline-block;    }    .active {      border-bottom: 5rpx solid #87CEEB;    }  }  /* #ifdef H5 */  .nav ::-webkit-scrollbar {    width: 0;    height: 0;    color: transparent;    display: none;  }  </style>

5.4. 实现导航栏的切换效果

index.vue

<script>  import config from '../../common/config.js'  export default {    data() {      return {        navList: [],        currentIndexNav: 0,        swiperList: [],        videosList: [],      }    },    onLoad() {      this.getNavList()      this.getSwiperList()      this.getVideosList()    },    methods: {      getNavList() {        uni.request({          url: config.url + '/navList',          success: res => {            this.navList = res.data.data.navList            console.log(this.navList) // 输出到控制台          }        })      },      activeNav(e) {        this.currentIndexNav = e.target.dataset.index      },    }  }</script>

6.轮播图区域

6.1. 加载轮播图数据

index.vue

<script>  import config from '../../common/config.js'  export default {    data() {      return {        navList: [],        currentIndexNav: 0,        swiperList: [],      }    },    onLoad() {      this.getNavList()      this.getSwiperList()    },    methods: {      getNavList() {        uni.request({          url: config.url + '/navList',          success: res => {            this.navList = res.data.data.navList            console.log(this.navList) // 输出到控制台          }        })      },      activeNav(e) {        this.currentIndexNav = e.target.dataset.index      },      getSwiperList() {        uni.request({          url: config.url + '/swiperList',          success: res => {            this.swiperList = res.data.data.swiperList          }        })      },    }  }</script>

6.2. 实现轮播图区域的页面结构

index.vue

<template>  <view>    <MyTitle></MyTitle>    <viewclass="nav_wrap">      <scroll-viewclass="nav"scroll-x>        <view @click="activeNav($event,index)":class="['nav_item',index===currentIndexNav?'active':'']"          :data-index="index" v-for="(item,index) in navList" :key="item.id">          {{item.text}}        </view>      </scroll-view>    </view>    <viewclass="slides">      <swiperautoplayindicator-dotscircular>        <swiper-itemv-for="(item,index) in  swiperList":key="index">          <image:src="item.imgSrc"mode="widthFix"></image>        </swiper-item>      </swiper>    </view>  </view></template>

6.3. 实现轮播图区域的页面样式

index.vue

<stylelang="scss">  .nav {    white-space: nowrap;    padding: 5rpx 0;    .nav_item {      padding: 20rpx 45rpx;      font-size: 30rpx;      display: inline-block;    }    .active {      border-bottom: 5rpx solid #87CEEB;    }  }  /* #ifdef H5 */  .nav ::-webkit-scrollbar {    width: 0;    height: 0;    color: transparent;    display: none;  }  /* #endif */  .slides {    margin: 10rpx 0;    swiper {      height: 220rpx;    }    image {      width: 100%;      height: 100%;    }  }</style>

7.视频列表区域

7.1. 加载视频列表数据

index.vue

<script>  import config from '../../common/config.js'  export default {    data() {      return {        navList: [],        currentIndexNav: 0,        swiperList: [],        videosList: [],      }    },    onLoad() {      this.getNavList()      this.getSwiperList()      this.getVideosList()    },    methods: {      getNavList() {        uni.request({          url: config.url + '/navList',          success: res => {            this.navList = res.data.data.navList            console.log(this.navList) // 输出到控制台          }        })      },      activeNav(e) {        this.currentIndexNav = e.target.dataset.index      },      getSwiperList() {        uni.request({          url: config.url + '/swiperList',          success: res => {            this.swiperList = res.data.data.swiperList          }        })      },      getVideosList() {        uni.request({          url: config.url + '/videosList',          success: res => {            this.videosList = res.data.data.videosList          }        })      },    }  }</script>

7.2. 实现视频列表区域的页面结构

index.vue

<template>  <view>    <MyTitle></MyTitle>    <viewclass="nav_wrap">      <scroll-viewclass="nav"scroll-x>        <view @click="activeNav($event,index)":class="['nav_item',index===currentIndexNav?'active':'']"          :data-index="index" v-for="(item,index) in navList" :key="item.id">          {{item.text}}        </view>      </scroll-view>    </view>    <viewclass="slides">      <swiperautoplayindicator-dotscircular>        <swiper-itemv-for="(item,index) in  swiperList":key="index">          <image:src="item.imgSrc"mode="widthFix"></image>        </swiper-item>      </swiper>    </view>    <viewclass="video-wrap">      <!-- 视频列表中的每一项 -->      <viewclass="video-item" @click="goVideosDetail(item.id)"v-for="(item,index) in videosList":key="item.id">        <!-- 图片容器 -->        <viewclass="video-img">          <image:src="item.imgSrc"mode="widthFix"></image>          <!-- 播放量 -->          <viewclass="video-info">            <!-- 播放量 -->            <viewclass="play-count-wrap">              <!-- 图标 -->              <textclass="fa fa-play-circle-o"></text>              <!-- 数值 -->              <textclass="play-count">{{ item.playCount }}</text>            </view>            <!-- 评论量 -->            <viewclass="comment-count-row">              <!-- 图标 -->              <textclass="fa fa-commenting-o"></text>              <!-- 数值 -->              <textclass="comment-count">{{ item.commentCount }}</text>            </view>          </view>        </view>        <!-- 标题 -->        <viewclass="video-title">          {{ item.desc }}        </view>      </view>    </view>  </view></template>

7.3. 实现视频列表区域的页面样式

index.vue

<stylelang="scss">  .nav {    white-space: nowrap;    padding: 5rpx 0;    .nav_item {      padding: 20rpx 45rpx;      font-size: 30rpx;      display: inline-block;    }    .active {      border-bottom: 5rpx solid #87CEEB;    }  }  /* #ifdef H5 */  .nav ::-webkit-scrollbar {    width: 0;    height: 0;    color: transparent;    display: none;  }  /* #endif */  .slides {    margin: 10rpx 0;    swiper {      height: 220rpx;    }    image {      width: 100%;      height: 100%;    }  }  .video-wrap {    display: flex;    flex-wrap: wrap;    justify-content: space-between;    padding: 5rpx;  }  .video-item {    width: 48%;    margin-bottom: 20rpx;  }  .video-img {    position: relative;    image {      width: 100%;      border-radius: 15rpx;    }    .video-info {      position: absolute;      bottom: 8rpx;      left: 0;      width: 100%;      display: flex;      justify-content: space-around;      background: rgba(0, 0, 0, 0.3);    }    .video-info text {      margin-right: 5rpx;      font-size: 24rpx;      color: #efefef;    }  }  .video-title {    font-size: 28rpx;    /* 将对象作为弹性伸缩盒子模型显示 */    display: -webkit-box;    /* 超出部分隐藏 */    overflow: hidden;    /* 显示的行数 */    -webkit-line-clamp: 2;    /* 从上到下垂直排列子元素 */    -webkit-box-orient: vertical;  }</style>

7.4. 实现跳转到视频详情页

index.vue

<script>  import config from '../../common/config.js'  export default {    data() {      return {        navList: [],        currentIndexNav: 0,        swiperList: [],        videosList: [],      }    },    onLoad() {      this.getNavList()      this.getSwiperList()      this.getVideosList()    },    methods: {      getNavList() {        uni.request({          url: config.url + '/navList',          success: res => {            this.navList = res.data.data.navList            console.log(this.navList) // 输出到控制台          }        })      },      activeNav(e) {        this.currentIndexNav = e.target.dataset.index      },      getSwiperList() {        uni.request({          url: config.url + '/swiperList',          success: res => {            this.swiperList = res.data.data.swiperList          }        })      },      getVideosList() {        uni.request({          url: config.url + '/videosList',          success: res => {            this.videosList = res.data.data.videosList          }        })      },      goVideosDetail(id) {        uni.navigateTo({          url: '/pages/detail/detail?id=' + id        })      },    }  }</script>

8.视频详情页

8.1. 实现整体页面结构

detail.vue

<template>  <view>    <MyTitle></MyTitle>    <!-- 视频详情 -->    <viewclass="video-info"v-if="videoInfo">    </view>    <!-- 推荐视频 -->    <viewclass="other-list">    </view>    <!-- 评论列表 -->    <viewclass="comment-wrap">    </view>  </view></template>

8.2. 实现视频详情区域

detail.vue

<script>  import config from "../../common/config.js"  export default {    data() {      return {        // 视频详情        videoInfo: null,      }    },    onLoad(options) {      let videoId = options.id      this.getCurrentVideo(videoId)    },    methods: {      // 页面加载时获取视频信息      getCurrentVideo(videoId) {        uni.request({          url: config.url + '/videoDetail?id=' + videoId,          success: res => {console.log(res.data.data)            if (res.data.code === 0) {              this.videoInfo = res.data.data.videoInfo            }          }        })      },    }  }</script>

detail.vue

<template>  <view>    <MyTitle></MyTitle>    <!-- 视频详情 -->    <viewclass="video-info"v-if="videoInfo">      <!-- 视频 -->      <video:src="videoInfo.videoSrc"controlsclass="video"></video>      <!-- 视频标题 -->      <viewclass="video-title">        <text>{{ videoInfo.videoTitle }}</text>        <textclass="fa fa-angle-down"></text>      </view>      <!-- 视频详细信息  -->      <viewclass="video-detail">        <!-- 作者 -->        <textclass="author">{{ videoInfo.author }}</text>        <!-- 播放量 -->        <textclass="play-count">{{ videoInfo.playCount }}</text>        <!-- 评论量 -->        <textclass="comment-count">{{ videoInfo.commentCount }}弹幕</text>        <!-- 时间 -->        <textclass="date">时间:{{ videoInfo.date }}</text>      </view>    </view>    <!-- 推荐视频 -->    <viewclass="other-list">    </view>    <!-- 评论列表 -->    <viewclass="comment-wrap">    </view>  </view></template>

8.3. 实现推荐视频区域

detail.vue

<stylelang="scss">  .video-info {    margin-top: 10rpx;    .video {      width: 100%;    }    .video-title {      display: flex;      justify-content: space-between;      font-size: 35rpx;    }    .video-detail {      margin-top: 5rpx;      font-size: 28rpx;      text {        margin-left: 15rpx;      }      .author {        color: #000;      }    }  }</style>

detail.vue

<script>  import config from "../../common/config.js"  export default {    data() {      return {        // 视频详情        videoInfo: null,        othersList: [],      }    },    onLoad(options) {      let videoId = options.id      this.getCurrentVideo(videoId)      this.getOthersList()    },    methods: {      // 页面加载时获取视频信息      getCurrentVideo(videoId) {        uni.request({          url: config.url + '/videoDetail?id=' + videoId,          success: res => {console.log(res.data.data)            if (res.data.code === 0) {              this.videoInfo = res.data.data.videoInfo            }          }        })      },      getOthersList() {        uni.request({          url: config.url + '/othersList',          success: res => {            if (res.data.code === 0) {              this.othersList = res.data.data.othersList            }          }        })      },    }  }</script>

detail.vue

  <!-- 推荐视频 -->    <viewclass="other-list">      <viewv-for="item in othersList":key="item.id"class="item_other">        <!-- 图片容器 -->        <viewclass="other-img-wrap">          <image:src="item.imgSrc"mode="widthFix"></image>        </view>        <!-- 视频详情 -->        <viewclass="other-info">          <!-- 标题 -->          <viewclass="other-title">{{ item.title }}</view>          <!-- 播放量 -->          <viewclass="other-detail">            <!-- 播放量 -->            <textclass="play-count"> {{ item.playMsg }}次观看 </text>            <!-- 评论 -->            <textclass="comment-count">{{ item.commentCount }}条评论</text>          </view>        </view>      </view>    </view>

detail.vue

<stylelang="scss">    .other-list {    margin-top: 10rpx;    .item_other {      display: flex;      justify-content: space-between;      margin-bottom: 20rpx;      .other-img-wrap {        width: 38%;        display: flex;        justify-content: center;        align-items: center;        .other-img-wrap image {          width: 90%;          border-radius: 15rpx;        }      }      .other-info {        width: 60%;        display: flex;        flex-direction: column;        justify-content: space-around;        .other-title {          font-size: 30rpx;          color: #e06f93;        }        .other-detail {          font-size: 26rpx;          color: #666;        }      }    }  }</style>

8.4. 实现评论列表区域

detail.vue

<script>  import config from "../../common/config.js"  export default {    data() {      return {        // 视频详情        videoInfo: null,        othersList: [],        commentData: [],      }    },    onLoad(options) {      let videoId = options.id      this.getCurrentVideo(videoId)      this.getOthersList()      this.getCommentList(videoId)    },    methods: {      // 页面加载时获取视频信息      getCurrentVideo(videoId) {        uni.request({          url: config.url + '/videoDetail?id=' + videoId,          success: res => {console.log(res.data.data)            if (res.data.code === 0) {              this.videoInfo = res.data.data.videoInfo            }          }        })      },      getOthersList() {        uni.request({          url: config.url + '/othersList',          success: res => {            if (res.data.code === 0) {              this.othersList = res.data.data.othersList            }          }        })      },      getCommentList(videoId) {        uni.request({          url: config.url + '/commentsList?id=' + videoId,          success: res => {            if (res.data.code === 0) {              this.commentData = res.data.data.commentData;            }          }        })      },    }  }</script>

detail.vue

<!-- 评论列表 -->    <viewclass="comment-wrap">      <viewclass="comment-title">        评论 ({{ commentData.commentTotalCount }} )      </view>      <viewclass="comment-list">        <viewclass="comment-item"v-for="(item, index) in commentData.commentList":key="index">          <!-- 左侧用户 -->          <viewclass="comment_user">            <image:src="item.userIconSrc"mode="widthFix"class="comment_user_image" />          </view>          <!-- 右侧评论 -->          <viewclass="comment-info">            <viewclass="comment-detail">              <textclass="author">{{ item.username }}</text>              <textclass="date">{{ item.commentDate }}</text>            </view>            <viewclass="comment-content">              {{ item.commentInfo }}            </view>          </view>        </view>      </view>    </view>

detail.vue

<stylelang="scss">.comment-wrap {margin-top: 10rpx;.comment-title {padding: 10rpx;font-size: 28rpx;		}.comment-list {.comment-item {margin-bottom: 10rpx;padding: 10rpx;display: flex;justify-content: space-between;border-bottom: 1px solid #666;			}.comment_user {flex: 1;display: flex;justify-content: center;align-items: center;.comment_user_image {width: 60%;border-radius: 50%;				}			}.comment-info {flex: 5;display: flex;flex-direction: column;justify-content: space-around;.comment-detail {display: flex;justify-content: space-between;.comment-detail .author {font-size: 28rpx;color: #000;					}.comment-detail .date {color: #666;font-size: 24rpx;					}				}			}.comment-content {font-size: 28rpx;color: #000;			}		}	}</style>

相关学习资料