欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

vue移动端项目-b站

程序员文章站 2024-03-14 10:05:46
...

仿bilibili移动端项目

项目介绍

  hello,又是我,学习生活闲暇之余我做了个移动端的vue项目,这次是模仿b站的(大佬别骂我~)。废话不多说,接下来开始正片:
  技术栈:vue-cli、vue、vue-router、axios、vantui、less
  技术方案:移动端flex+vw布局、组件化开发

效果截图

注册页
vue移动端项目-b站
登录页
vue移动端项目-b站
登录成功(主页)
vue移动端项目-b站
视频页
vue移动端项目-b站
vue移动端项目-b站
我的
vue移动端项目-b站
修改个人资料
vue移动端项目-b站
个人资料修改
vue移动端项目-b站

代码

通用组件-登录头部、文本、按钮,普通页面头部(顺序):

<template>
  <div>
    <div id="lgtop">
      <div></div>
      <div>{{ middletop }}</div>
      <div>
        <slot name="righttop" />
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: ["middletop"],
};
</script>

<style lang="less">
#lgtop {
  height: 11.111vw;
  background-color: #fff;
  display: flex;
  div {
    flex: 1;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 4vw;
  }
//   div:nth-child(3){
//       justify-content: flex-end;
      
//   }
}
</style>
<template>
  <div>
      <van-field v-model="content" :label="label" :type="type" :placeholder="placeholder" :rule="rule" />
  </div>
</template>

<script>
export default {
    data(){
        return{
            content: ''
        }
    },
     props: ["label","type","placeholder","rule"],
     watch:{
         //监听data数据里面content的变化
         content(){
             this.testinput();
         }
     },
     methods:{
         //判断正则的方法
         testinput(){
             //待解决bug:当输入完后删除重新输入,当你重新输入的值不到6位时,content里面的值还是原来的值
             const rule = new RegExp(this.rule);
             if(rule.test(this.content)){
                 this.$emit("contentright",this.content);
             }
         }
     }
};
</script>
<style>
</style>
<template>
  <div class="parentbox">
    <div class="*n" @click="lgorrgsummit">{{btntext}}</div>
  </div>
</template>

<script>
export default {
  props: ["btntext"],
  methods:{
    lgorrgsummit(){
      this.$emit('lgorrgsummit');
    }
  }
};
</script>

<style>
.parentbox{
    padding: 3.333vw 2.222vw;
}
.*n {
  height: 10vw;
  background-color: #ff9db5;
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 5vw;
  color: white;
}
</style>

主页头部:

<template>
  <div>
    <van-tabs v-model="active" swipeable sticky>
      <van-tab
        v-for="(item, index) in category"
        :key="index"
        :title="item.title"
      >
        <van-list
          v-model="item.loading"
          :finished="item.finished"
          finished-text="加载完毕了"
          @load="onLoad"
          :immediate-check="false"
        >
          <div class="detailparent">
            <detail
              class="detailchild"
              :articleitem="listitem"
              v-for="(listitem, listindex) in item.list"
              :key="listindex"
            ></detail>
          </div>
        </van-list>
      </van-tab>
    </van-tabs>
  </div>
</template>

<script>
import detail from "@/components/home/homedetail.vue";
export default {
  components: {
    detail,
  },
  data() {
    return {
      category: [],
      active: 0,
    };
  },
  created() {
    this.getcategory();
  },
  watch: {
    active() {
      this.getarticle();
    },
  },
  methods: {
    // 获取分类
    async getcategory() {
      const res = await this.$http.get("/category");
      this.changecategory(res.data);
    },
    changecategory(data) {
      const res = data.map((item, index) => {
        item.list = [];
        item.loading = false,
        item.finished = false,
        item.page = 0;
        item.pagesize = 10;
        return item;
      });
      this.category = res;
      this.getarticle();
    },
    //获取视频
    async getarticle() {
      //由于active会根据用户点击而改变,所以active为当前的导航栏的下标
      //根据当前下标可以找到对应当前的category
      //item为当前的category
      const item = this.category[this.active];
      const res = await this.$http.get("/detail/" + item._id, {
        params: {
          page: item.page,
          pagesize: item.pagesize,
        },
      });
      //这里需要push是因为下拉滚动加载后会更新新的数据,如果不用push会直接覆盖之前的数据
      item.list.push(...res.data);
      item.loading = false;
      //这里加载完要提示加载完毕,当最新获取的一次数据的长度<当前pasgsize的长度时,说明加载完了
      if(res.data.length < item.pagesize){
          item.finished = true;
      }
    },
    //滑动到底部触发事件
    onLoad(){
        //滚动到底部时,确定要加载的是哪个分类,然后将分类下的对应的渲染数据的页数自增
        const item = this.category[this.active];//例如当前分类为首页,那么获取的item为首页下的数据
        item.page += 1;
        this.getarticle();

    }
  },
};
</script>

<style lang="less">
body {
  padding: 0;
  margin: 0;
  background-color: #fff;
}
.detailparent {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-around;
  align-content: center;
  margin-top: 2.222vw;
  .detailchild {
    width: 45%;
  }
}
</style>

主页body:

<template>
  <div class="detailbox" @click="routepush">
    <div class="detail">
      <img :src="articleitem.img" alt="" />
      <div class="bottom">
        <div class="play">
          <van-icon name="play-circle-o" /><span>1165</span>
        </div>
        <div class="comment">
          <van-icon name="comment-o" /><span>{{ articleitem.commentlen }}</span>
        </div>
      </div>
    </div>
    <p>{{ articleitem.name }}</p>
  </div>
</template>

<script>
export default {
  props: ["articleitem"],
  methods: {
    routepush() {
      if (this.$route.path != `/videodetail/${this.articleitem.id}`) {
        this.$router.push(`/videodetail/${this.articleitem.id}`);
      }
    },
  },
};
</script>

<style lang="less">
.detailbox {
  .detail {
    position: relative;
    img {
      width: 100%;
    }
    .bottom {
      width: 100%;
      position: absolute;
      bottom: 0;
      left: 0;
      display: flex;
      justify-content: space-between;
      background: linear-gradient(0deg, rgba(0, 0, 0, 0.85), transparent);
      div {
        padding: 1.111vw 2.222vw;
        color: #fff;
        display: flex;
      }
    }
  }
  p {
    font-size: 14px;
  }
}
</style>

详情页:

<template>
  <div v-if="data!=null">
    <nav-header></nav-header>
    <div class="allinfo">
      <div class="videobox">
        <video class="video" :src="data.content" controls="controls"></video>
      </div>
      <div class="viedoname">
        <span class="categoryname">{{ data.category.title }}</span>
        <span class="titlename">{{ data.name }}</span>
      </div>
      <div class="userinfo">
        <span class="authname">{{ data.userinfo.name }}</span>
        <span class="looked">149.6万观看</span>
        <span class="say">2.6万弹幕</span>
        <span class="date">{{ data.date }}</span>
      </div>
      <div class="threeclick">
        <div><van-icon size="5.556vw" name="good-job" /><span>点赞</span></div>
        <div><van-icon size="5.556vw" name="star" /><span>收藏</span></div>
        <div><van-icon size="5.556vw" name="down" /><span>缓存</span></div>
      </div>
    </div>
    <div class="detailparent">
      <detail
        class="detailchild"
        v-for="(item, index) in commend"
        :key="index"
        :articleitem="item"
      ></detail>
    </div>
    <div class="commend">
      <commendtitle></commendtitle>
    </div>
  </div>
</template>

<script>
import NavHeader from "@/components/comment/nav.vue";
import detail from "@/components/home/homedetail.vue";
import commendtitle from "@/components/video/commendtitle.vue";
export default {
  data() {
    return {
      data: null,
      commend: [],
    };
  },
  components: {
    NavHeader,
    detail,
    commendtitle,
  },
  created() {
    this.getvideodata();
    this.getcommend();
  },
  methods: {
    async getvideodata() {
      // console.log(this.$route);
      const res = await this.$http.get("/article/" + this.$route.params.id);
      // console.log(res);
      this.data = res.data[0];
    },
    async getcommend() {
      const res = await this.$http.get("/commend");
      console.log(res);
      this.commend = res.data;
    },
  },
  watch: {
    //对路由监控,当推荐文章的数据点击后路由跳转改变,进行重新渲染页面
    $route() {
      this.getvideodata();
      this.getcommend();
    },
  },
};
</script>

<style lang="less">
body{
  background-color: #fff;
}
.allinfo {
  background-color: #fff;
  .videobox {
    width: 102vw;
    .video {
      width: 100%;
    }
  }
  .viedoname {
    span {
      display: inline-block;
      font-size: 4vw;
    }
    .categoryname {
      padding: 0 2.222vw;
      margin: 3.333vw;
      color: #fb7299;
      background-color: #f4f4f4;
      border-radius: 2.222vw;
      text-align: center;
    }
  }
  .userinfo {
    margin: 1.111vw 4.444vw;
    .authname {
      padding: 2.222vw 3.333vw 2.222vw 2.222vw;
    }
    span {
      font-size: 3.111vw;
    }
    .looked,
    .say,
    .date {
      padding: 1.111vw;
      color: #999999;
    }
  }
  .threeclick {
    display: flex;
    div {
      display: flex;
      margin: 2.222vw 3.333vw;
      font-size: 4vw;
    }
  }
}
.detailparent {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-around;
  align-content: center;
  margin-top: 2.222vw;
  .detailchild {
    width: 45%;
  }
}
</style>

个人页面:

<template>
  <div class="userbox">
    <div>
      <div class="userimg">
          <img v-if="userdata.user_img" :src="userdata.user_img" alt="">
        <img v-else src="../../assets/picture.jpg" alt="" />
      </div>
      <div class="rightbox">
        <div class="msg">
          <p><span>0</span> <span>粉丝</span></p>
          <p><span>54</span> <span>关注</span></p>
          <p><span>0</span> <span>获赞</span></p>
        </div>
        <div class="edit" @click="$router.push('/editdata')">编辑资料</div>
      </div>
    </div>
    <div class="usermsg">
      <div class="name"><p>{{userdata.name}}</p></div>
      <div class="introduce">
          <span v-if="userdata.user_desc">{{userdata.user_desc}}</span>
          <span v-else>这个人很神秘,什么都没有写</span>
          <span>展开</span>
      </div>
    </div>
    <div class="dynamic">
        <span>动态</span>
        <span>视频</span>
    </div>
  </div>
</template>

<script>
export default {
    props:['userdata']
    // --------------------------------做到发送请求,获取页面数据,接着渲染数据到页面上
};
</script>

<style lang="less">
.userbox {
  height: 44.444vw;
  background-color: #fff;

  > div:nth-child(1) {
    display: flex;
    padding: 0 30px;
    .userimg {
      width: 22.222vw;
      height: 22.222vw;
      border-radius: 50%;
      overflow: hidden;
      margin-right: 6.667vw;
      img {
        width: 22.222vw;
        height: 22.222vw;
        // margin-top: -1.111vw;
      }
    }
    .rightbox {
      flex: 1;
      .msg {
        display: flex;
        p {
          flex: 1;
          display: flex;
          flex-direction: column;
          justify-content: center;
          align-items: center;
          font-size: 3.111vw;
          position: relative;
          span:nth-child(2) {
            color: #999;
          }
          &:after {
            content: "";
            width: 0.222vw;
            height: 5.556vw;
            background-color: #e7e7e7;
            position: absolute;
            top: 1.111vw;
            right: 0;
          }
          &:nth-child(3):after {
            display: none;
          }
        }
      }
      .edit {
        border: 0.222vw solid #fb7299;
        color: #fb7299;
        text-align: center;
        padding: 2vw 5px;
        border-radius: 1.111vw;
      }
    }
  }
  .usermsg {
    padding: 0;
    width: 101.778vw;
    padding: 0 3.333vw;
    .name {  
        p{
            font-size: 4.444vw;
        }
    }
    .introduce{
        position: relative;
        margin: -2.222vw 0 3.333vw 0;
        span:nth-child(1){
            color: #999;
        }
        span:nth-child(2){
            color: #1389bf;
            position: absolute;
            bottom: 0;
            right: 10vw;
        }
    }
  }
  .dynamic{
      height: 11.111vw;
      border-top: 0.222vw solid #ccc;
      background-color: #fff;
      span{
          display: inline-block;
          padding: 3.333vw;
      }
      span:nth-child(1){
          color: #fb7299;
      }
      span:nth-child(2){
          color: #757575;
      }
  }
}
</style>

个人总结

  这个项目是晚上空闲的时间写的,用的接口是公网的接口,是一个模块化、组件化开发的前后端分离模式项目,做到对自己的前端模块化组件化思想更深一步的理解,期间也遇到了一些困难,不断的查找资料,百度谷歌csdn三管齐下,总算坎坷的走了过来,希望在未来的路上能有更深的学习和表现吧!前端开发冲啊!