介绍
本次项目是移动应用网页课程的大作业,项目使用github上一开源项目学习开发而来。 https://github.com/axhello/douyu-vue 本项目基本就是斗鱼手机移动端的复刻,除了没有评论功能,以外功能都差不多,其后端也是对接的官方的api接口。 源码压缩包地址 https://download.csdn.net/download/david2000999/85656221
学习建议
- 本项目可以帮助初学者更好理解完整的前后端开发模式,如何进行前后端数对接,其中格外注意数据传输和展示的格式
- 对于想要业务功能的同学,可以直接搭建一个后端自己的数据库,承接自己新业务上的数据,比如开发自己的评论区,和一些数据统计页面,添加数据分析功能等等。
使用
- 首先建立空文件打开控制台克隆代码到本地
git clone https://github.com/axhello/douyu-vue.git
- 然后进行相关依赖安装(前提本地安装好了npm 和 node)
npm install
npm run serve
- 运行后就可以得到
浏览器直接访问即可 其中第二个局域网地址,可以让电脑连接手机热点后再启动项目,就可以使用第二个的局域网地址再手机上浏览器上进行直接访问了,可以更好体验移动端的适配性。
业务分区
(1)首页:最新活动也展示和热度最高的直播间 (2)分区页:星势力,网游竞技,单机热游,手游休闲,娱乐天地,科技文化,语音互动,语音直播,正能量,赛车竞技,京斗云 (3)分类页:各个具体分区页中的再分类页 (4)推荐区:直播区和视频区下方还有类似内容推荐内容 (5)搜索区:搜索栏中进行定向内容的搜索
功能设计
(1)直播内容展示 (2)搜索相关内容 (3)直播内容分类 (4)推荐相关直播内容 (5)热点内容滚动展示 (6)获取斗鱼安装下载
软件架构
运行截图
核心代码
category.vue
<template>
<div class="n-list clearfix">
<a
v-for="(catelist, index) in catelists"
:key="index"
class="n-list-item"
@click="rooms(catelist)">
<img class="live-feature" :src="catelist.icon">
<p class="title">
{{ catelist.cate2Name }}
</p>
</a>
</div>
</template>
<script>
import { category } from '@/api/mobile'
export default {
name: 'Category',
data() {
return {
catelists: []
}
},
watch: {
'$route.params.id'() {
this.fetchData()
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
category({ type: '' }).then(response => {
console.log(response.data)
const cate1Id = this.$route.params.id
const cate2Info = response.data.cate2Info
const result = cate2Info.filter(cate2 => cate2.cate1Id === cate1Id)
this.catelists = result
})
},
rooms(catelist) {
this.$cookie.set('gameName', catelist.cate2Name)
this.$router.push({
name: 'rooms',
params: { name: catelist.shortName }
})
}
}
}
</script>
<style lang="scss">
.n-list {
width: 100%;
height: 100%;
overflow-y: auto;
background-color: #f4f4f4;
z-index: 4;
.n-list-item {
margin: 0;
padding: 0;
float: left;
width: 3.3rem;
height: 3.3rem;
border-right: 1px dashed #ddd;
border-bottom: 1px dashed #ddd;
text-align: center;
font-size: 0.32rem;
color: #000;
margin-left: 1px;
&:nth-child(3n) {
border-right: none;
}
img {
margin-top: 0.66666666rem;
margin-bottom: 0.133333333rem;
width: 1.62666666rem;
height: 1.62666666rem;
border-radius: 0.8133333rem;
}
}
}
</style>
detail.vue
<template>
<iframe
class="dy-inframe"
frameborder="0"
:src="roomId"></iframe>
</template>
<script>
export default {
name: 'RoomDetail',
computed: {
roomId() {
return '//m.douyu.com/' + this.$route.params.id
}
}
}
</script>
<style lang="scss">
.dy-inframe {
width: 100%;
height: 1050px;
overflow-x: hidden;
overflow-y: auto;
}
</style>
home.vue
<template>
<section class="content">
<div class="m-row">
<v-swiper />
<div class="live-list">
<router-link
v-for="(livelist, index) in livelists"
:key="index"
:to="{name: 'detail', params: { id: livelist.rid }}"
class="live">
<img
class="live-feature"
:src="livelist.roomSrc">
<div class="live-title">
{{ livelist.roomName }}
</div>
<div class="live-info">
<span class="dy-name">
{{ livelist.nickname }}
</span>
<span class="popularity">
{{ livelist.hn }}
</span>
</div>
</router-link>
</div>
</div>
</section>
</template>
<script>
import VSwiper from '@/components/v-swiper'
import { home } from '@/api/mobile'
export default {
components: {
VSwiper
},
data() {
return {
livelists: []
}
},
created() {
this.fetchData()
},
methods: {
fetchData() {
home().then(response => {
console.log(response.data[0].list)
this.livelists = response.data[0].list
})
}
}
}
</script>
<style lang="scss">
.m-row {
background: #fff;
}
.live-list {
position: relative;
box-sizing: border-box;
padding: 0 0.13333333rem 0.13333333rem;
width: 100%;
&:after {
display: block;
content: '';
clear: both;
}
.live {
float: left;
position: relative;
display: block;
margin: 0.13333333rem;
width: 4.6rem;
height: 3.28rem;
color: #333;
font-size: 12px;
.live-feature {
position: absolute;
display: block;
top: 0;
left: 0;
width: 100%;
height: 2.61333333rem;
background-color: #000;
border-radius: 0.1rem;
}
.live-title {
position: absolute;
bottom: 0;
left: 0.2rem;
width: 4rem;
color: #000;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
line-height: 0.66666667rem;
}
.live-info {
position: absolute;
bottom: 0.66666667rem;
left: 0;
width: 100%;
color: #fff;
border-bottom-left-radius: 0.2rem;
border-bottom-right-radius: 0.2rem;
background: linear-gradient(
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.1) 30%,
rgba(0, 0, 0, 0.8) 100%
);
.dy-name {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 2.746666666rem;
display: inline-block;
padding-left: 0.1rem;
}
.popularity {
float: right;
padding-right: 0.2rem;
}
}
}
}
</style>
rooms.vue
<template>
<div class="m-row">
<div class="title">
<svg-icon icon-class="tv" />
<span>{{ cateName }}</span>
<strong>{{ gameName }}</strong>
</div>
<div class="live-list clearfix">
<router-link
v-for="(roomlist, index) in roomlists"
:key="index"
:to="{name: 'detail', params: {id: roomlist.rid}}"
class="live">
<img class="live-feature" :src="roomlist.roomSrc">
<div class="live-title">
{{ roomlist.roomName }}
</div>
<div class="live-info">
<span class="dy-name">
{{ roomlist.nickname }}
</span>
<span class="popularity">
{{ roomlist.hn }}
</span>
</div>
</router-link>
</div>
<v-more-button>
<div v-if="!hidden" class="more-button">
<div v-show="!loading" @click="loadMore">
加载更多
</div>
<div v-show="loading" class="ball-pulse">
<div></div>
<div></div>
<div></div>
</div>
</div>
</v-more-button>
</div>
</template>
<script>
import VMoreButton from '@/components/v-more-button'
import { rooms } from '@/api/mobile'
export default {
name: 'Rooms',
components: {
VMoreButton
},
props: {
name: {
type: String,
default: ''
}
},
data: () => ({
roomlists: [],
params: {
name: '',
page: 1
},
hidden: false,
loading: false,
cateName: '',
gameName: ''
}),
created() {
this.fetchData()
},
mounted() {
this.cateName = this.$cookie.get('cateName')
this.gameName = this.$cookie.get('gameName')
},
methods: {
fetchData() {
rooms({ page: this.params.page, type: this.name }).then(response => {
this.loading = false
this.roomlists = response.data.list
})
},
loadMore() {
this.loading = true
this.params.page = ++this.params.page
this.fetchData()
}
}
}
</script>
<style lang="scss" scoped>
.m-row {
.play-icon {
width: 0.3999999rem;
height: 0.3999999rem;
vertical-align: sub;
}
.title {
font-size: 12px;
margin-left: 10px;
line-height: 0.8333333rem;
strong {
margin-left: 4px;
color: #fa7122;
}
}
}
.live-list {
.live {
float: left;
position: relative;
display: block;
margin: 0.13333333rem;
width: 4.6rem;
height: 3.28rem;
color: #333;
font-size: 12px;
.live-feature {
position: absolute;
display: block;
top: 0;
left: 0;
width: 100%;
height: 2.61333333rem;
background-color: #000;
border-radius: 0.2rem;
}
.live-title {
position: absolute;
bottom: 0;
left: 0.2rem;
width: 4rem;
color: #000;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
line-height: 0.66666667rem;
}
.live-info {
position: absolute;
bottom: 0.66666667rem;
left: 0;
width: 100%;
color: #fff;
border-bottom-left-radius: 0.2rem;
border-bottom-right-radius: 0.2rem;
background: linear-gradient(rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 0.1) 30%, rgba(0, 0, 0, 0.8) 100%);
.dy-name {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
max-width: 2.746666666rem;
display: inline-block;
padding-left: 0.2rem;
}
.popularity {
float: right;
padding-right: 0.2rem;
}
}
}
}
</style>
|