vue移动端项目-b站
程序员文章站
2024-03-14 10:05:46
...
项目介绍
hello,又是我,学习生活闲暇之余我做了个移动端的vue项目,这次是模仿b站的(大佬别骂我~)。废话不多说,接下来开始正片:
技术栈:vue-cli、vue、vue-router、axios、vantui、less
技术方案:移动端flex+vw布局、组件化开发
效果截图
注册页
登录页
登录成功(主页)
视频页
我的
修改个人资料
个人资料修改
代码
通用组件-登录头部、文本、按钮,普通页面头部(顺序):
<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三管齐下,总算坎坷的走了过来,希望在未来的路上能有更深的学习和表现吧!前端开发冲啊!
上一篇: C++11新特性
下一篇: kafka常用的命令
推荐阅读
-
vue移动端项目-b站
-
详解vue中移动端自适应方案
-
vue移动端,解决focus无效、不兼容的问题
-
vue 移动端记录页面浏览位置的方法
-
vue 项目通过 HBuilderX 打包成 apk 移动安装包教程
-
也是无聊!用vue来输出B站的随机搜索关键词图片
-
利用HBuilder将vue项目打包成移动端app,运行页面空白问题解决
-
(三)01 -Vue项目打包发布移动App——vue.config.js中配置相对路径publicPath为空字符串 & 在public中添加HBuilderX的打包配置文件manifest.json
-
Java web移动端项目实现与微信小程序间通信(测试移动端调用微信扫一扫功能)
-
在vue移动端中实现日期选择组件