Vue+Django音乐网站

2024-4-20 302 4/20

Vue+Django音乐网站

Vue+Django音乐网站 Vue+Django音乐网站Vue+Django音乐网站 Vue+Django音乐网站 Vue+Django音乐网站

开始

后端

项目配置

在项目中的settings.py文件中进行配置

数据库配置

DATABASES = {  # 配置数据库
    'default': {
        # 在models.py中调用数据库
        # 'ENGINE': 'django.db.backends.sqlite3',#sqlite3数据库
        # 'NAME': BASE_DIR / 'db.sqlite3',#默认的文件数据库
        'ENGINE': 'django.db.backends.mysql',  # mysql数据库
        'NAME': 'music1',  # 数据库名字
        'USER': 'root',  # 账号
        'PASSWORD': '123456',  # 密码
        'HOST': '127.0.0.1',  # url
        'PORT': '3306',  # 端口
    }
}

中文配置

LANGUAGE_CODE = 'zh-hans'

TIME_ZONE = 'Asia/Shanghai'

文件路径配置

STATIC_URL = 'static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, 'static'),
)

跨域配置

MIDDLEWARE = [
    # 'django.middleware.csrf.CsrfViewMiddleware',
]

CORS_ORIGIN_ALLOW_ALL = True  # 允许任何来源的请求

APP配置

INSTALLED_APPS=[
    ...
    'simpleui',
    'APP',
]

模型

模型类

from django.db import models

# Create your models here.
class User(models.Model):
    """
    用户模型
    """
    GENDER_CHOICES = (
        ('M', '男'),
        ('F', '女'),
    )
    ROLE_CHOICES = (
        ('0', '管理员'),
        ('1', '普通用户'),
    )
    STATUS_CHOICES = (
        ('0', '正常'),
        ('1', '封号'),
    )
    id = models.BigAutoField(primary_key=True)  # 用户ID
    username = models.CharField(max_length=50, null=True)  # 用户名mi
    password = models.CharField(max_length=50, null=True)  # 密码
    role = models.CharField(max_length=2, blank=True, null=True)  # 用户角色
    status = models.CharField(max_length=1, choices=STATUS_CHOICES, default='0')  # 用户状态
    nickname = models.CharField(blank=True, null=True, max_length=20)  # 昵称
    avatar = models.FileField(upload_to='static/avatar/', null=True)  # 头像
    mobile = models.CharField(max_length=13, blank=True, null=True)  # 手机号
    email = models.CharField(max_length=50, blank=True, null=True)  # 邮箱
    gender = models.CharField(max_length=1, choices=GENDER_CHOICES, blank=True, null=True)  # 性别
    description = models.TextField(max_length=200, null=True)  # 描述
    create_time = models.DateTimeField(auto_now_add=True, null=True)  # 创建时间
    score = models.IntegerField(default=0, blank=True, null=True)  # 积分
    push_email = models.CharField(max_length=40, blank=True, null=True)  # 推送邮箱
    push_switch = models.BooleanField(blank=True, null=True, default=False)  # 推送开关
    admin_token = models.CharField(max_length=32, blank=True, null=True)  # 管理员令牌
    token = models.CharField(max_length=32, blank=True, null=True)  # 令牌
    attention_count = models.IntegerField(default=0, blank=True, null=True, verbose_name='关注数')
    followers_count = models.IntegerField(default=0, blank=True, null=True, verbose_name='粉丝数')
    likenum_count = models.IntegerField(default=0, blank=True, null=True, verbose_name='点赞数')


def __str__(self):
    return self.username


class Meta:
    db_table = "b_user"
    verbose_name_plural = '用户'


class Tag(models.Model):
    """
    标签模型
    """
    id = models.BigAutoField(primary_key=True)  # 标签ID
    title = models.CharField(max_length=100, blank=True, null=True)  # 标签名称
    create_time = models.DateTimeField(auto_now_add=True, null=True)  # 创建时间

    class Meta:
        db_table = "b_tag"
        verbose_name_plural = '标签'

    def __str__(self):
        return self.title


class Classification(models.Model):
    """
    分类模型
    """
    list_display = ("title", "id")
    id = models.BigAutoField(primary_key=True)  # 分类ID
    pid = models.IntegerField(blank=True, null=True, default=-1)  # 父分类ID
    title = models.CharField(max_length=100, blank=True, null=True)  # 分类名称
    create_time = models.DateTimeField(auto_now_add=True, null=True)  # 创建时间

    def __str__(self):
        return self.title

    class Meta:
        db_table = "b_classification"
        verbose_name_plural = '分类'


class Music(models.Model):
    """
    音乐模型
    """
    STATUS_CHOICES = (
        ('0', '启用'),
        ('1', '禁用'),
    )
    id = models.BigAutoField(primary_key=True)  # ID
    name = models.CharField(max_length=20, blank=False, null=False, verbose_name='名字')  # 名字
    singer = models.CharField(max_length=20, blank=False, null=False, verbose_name='歌手')  # 歌手
    file = models.FileField(upload_to='static/music/', null=True, verbose_name='文件')  # 文件
    cover = models.ImageField(upload_to='static/cover/', null=True, verbose_name='封面')  # 封面
    description = models.TextField(max_length=1000, blank=False, null=False, verbose_name='描述')  # 描述
    time = models.TextField(max_length=10, blank=True, null=True, verbose_name='时长')  # 时长
    classification = models.ForeignKey(Classification, on_delete=models.CASCADE, blank=True, null=True,
                                       verbose_name='类别')  # 类别
    tag = models.ManyToManyField(Tag, blank=True, verbose_name='标签')  # 标签
    create_time = models.DateTimeField(auto_now_add=True, null=True, verbose_name='创建时间')  # 创建时间
    pv = models.IntegerField(default=0, verbose_name='浏览量')  # 浏览量
    status = models.CharField(max_length=1, choices=STATUS_CHOICES, default='0', verbose_name='状态')  # 状态
    score = models.IntegerField(default=0, verbose_name='评分')  # 评分
    recommend_count = models.IntegerField(default=0, verbose_name='推荐数')  # 推荐数
    collect = models.ManyToManyField(User, blank=True, verbose_name='收藏的用户')  # 收藏的用户
    collect_count = models.IntegerField(default=0, verbose_name='收藏数')  # 收藏商品的数量

    def __str__(self):
        return self.name

    class Meta:
        db_table = "b_music"
        verbose_name_plural = '音乐'


class Banner(models.Model):
    """
    轮播图模型
    """
    id = models.BigAutoField(primary_key=True)  # 图片ID
    image = models.ImageField(upload_to='static/banner/', null=True)  # 图片
    describe = models.CharField(max_length=100, blank=True, null=True, )  # 描述

    create_time = models.DateTimeField(auto_now_add=True, null=True)  # 创建时间

    def __str__(self):
        return self.describe

    class Meta:
        db_table = "b_banner"
        verbose_name_plural = '轮播图'

admin后台管理

from django.contrib import admin
from APP import models

admin.site.register(models.User)
admin.site.register(models.Tag)
admin.site.register(models.Classification)
admin.site.register(models.Music)
admin.site.register(models.Banner)

api路径配置

urlpatterns = [
    path('', views.main),
    path('admin/', admin.site.urls),
    path('getBanner/',views.getBanner),
    path('getMusic/',views.getMusic),
    path('getPage/',views.getPage),
]

视图函数

前端主页

def main(request):
    return redirect('http://localhost:5173/main')

轮播图

def getBanner(request):
    print('请求轮播图数据')
    img_list = []


    for i in models.Banner.objects.all()[0:4]:
        img = cv2.imread(str(i.image))
        img = cv2.imencode('.jpg', img)[1]
        image_code = str(base64.b64encode(img))[2:-1] + ','
        img_list.append(image_code)
    return HttpResponse(img_list)

音乐数据

通过模型类对音乐数据表进行查询

在data列表中添加音乐数据的字典对象

img图片通过cv2读取从数据库中得到的图片文件

更换图片的编码为jpg

将图片转为beast64数据进行传输,前端网页对这种数据进行分析使用

最后通过JsonResponse将数据解析为json后发送

def getMusic(request):
    print('请求音乐数据')
    model = models.Music.objects.all()
    data = []
    for i in model:
 
        obj = {}
        obj['id'] = i.id
        obj['name'] = i.name
        obj['singer'] = i.singer

        img = cv2.imread(str(i.cover))

        img = cv2.imencode('.jpg', img)[1]
        image_code = str(base64.b64encode(img))[2:-1]
        obj['cover'] = image_code

        with open(str(i.file), 'rb') as f:
            mp3_data = f.read()
            bytes = str(base64.b64encode(mp3_data))[2:-1]
            obj['file'] = bytes
        obj['time'] = i.time

        data.append(obj)
    return JsonResponse(data, safe=False)

页面数据

def getPage(request):
    print('请求页面数据')

    id = request.GET.get('request[id]')
    i = models.Music.objects.get(id=id)
    obj = {}
    obj['id'] = i.id
    obj['name'] = i.name
    obj['singer'] = i.singer

    img = cv2.imread(str(i.cover))
    img = cv2.imencode('.jpg', img)[1]
    image_code = str(base64.b64encode(img))[2:-1]
    obj['cover'] = image_code
    time_obj = datetime.datetime.fromisoformat(str(i.create_time))

    # 构造只保留年月日的时间字符串
    new_time_str = time_obj.strftime("%Y-%m-%d")
    obj['create_time'] = new_time_str
    return JsonResponse(obj, safe=False)

类别分类

def getPage(request):
    print('请求页面数据')

    id = request.GET.get('request[id]')
    i = models.Music.objects.get(id=id)
    obj = {}
    obj['id'] = i.id
    obj['name'] = i.name
    obj['singer'] = i.singer

    img = cv2.imread(str(i.cover))
    img = cv2.imencode('.jpg', img)[1]
    image_code = str(base64.b64encode(img))[2:-1]
    obj['cover'] = image_code
    time_obj = datetime.datetime.fromisoformat(str(i.create_time))

    # 构造只保留年月日的时间字符串
    new_time_str = time_obj.strftime("%Y-%m-%d")
    obj['create_time'] = new_time_str
    return JsonResponse(obj, safe=False)

启动后端服务

py manage runserver

前端

前端采用了Node20.0、Element-ui、Vue、Axios

项目配置

使用Node20.0创建Vue项目,安装所需要的依赖包

npm install element-plus
npm install axios

在main.js文件中配置

此处对element中的组件、css、icon图标进行全局配置

import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'  // 引入路由模块

import ElementPlus from 'element-plus'//element
import 'element-plus/dist/index.css'//element的css
import * as ElementPlusIconsVue from '@element-plus/icons-vue'

const app = createApp(App)

app.use(router)
app.use(ElementPlus)//挂在element-ui
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {//导入icon图标
    app.component(key, component)
}

app.mount('#app')

vite.config,js中配置

在浏览器中有一种安全措施,他会禁止不同程序之间的通讯,我们需要对此进行配置

当我们的axios请求时的url为/api/getPage/时,相当于我们访问了http://127.0.0.1:8000/getPage

这里将url中的'/api'替换为空

import {fileURLToPath, URL} from 'node:url'

import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
    plugins: [vue()],
    resolve: {
        alias: {
            '@': fileURLToPath(new URL('./src', import.meta.url))
        },
    },
    server: {
        // 代理
        proxy: {
            '/api': {
                target: 'http://127.0.0.1:8000/', // 代理后台服务器地址
                changeOrigin: true, //允许跨域
                rewrite: path => path.replace(/^\/api/, '') // 将请求地址中的 /api 替换成空
            },
        }
    }
})

路由配置

在src下创建router/index.js文件

这里对可访问的url进行配置

默认访问的是/main的页面

import {createRouter, createWebHistory} from 'vue-router'

const routes = [
    {
        path: '/',
        redirect: '/main'
    },
    {
        path: '/main',
        name: 'main',
        component: () => import('@/views/Main.vue')  // 渲染该组件
    },
    {
        path: '/home',
        name: 'home',
        component: () => import('@/views/Page.vue')  // 渲染该组件
    },
    {
        path: '/test2',
        name: 'test2',
        component: () => import('@/views/MyMusic.vue')  // 渲染该组件
    },
    {
        path: '/page',
        name: 'page',
        component: () => import('@/views/Page.vue')  // 渲染该组件
    },
]

const router = createRouter({
    history: createWebHistory(),//采用html5路由模式
    routes
})//创建路由对象

export default router

视图

入口视图

此处代码定义了全局语言为中文

顶部的导航栏:搜索框、导航

导航栏中访问的url路径:主页、我的音乐、分类歌单、数字专辑、个人管理

视图位置:使用了Element中的布局方法,将音乐播放器组件放入最底下(绝对定位,防止被其他组件遮挡)

<el-menu>导航栏</el-menu>
<el-container>
    <el-main>导航视图</el-main>
    <el-footer>页脚</el-footer>
</el-container>
<music/>
Vue+Django音乐网站
Vue+Django音乐网站

主页视图

此处视图会显示在入口视图的<el-main>布局位置中

这个页面使用了Element中的布局方法包含了

轮播图

使用Element中的标签组件,定义了轮播图的数据、切换速度、轮播图类型、轮播图样式、轮播图片、图片张数

<el-carousel>
    <el-carousel-item>
         <el-image/>    
    </el-carousel-item>
</el-carousel>
数据切换导航

在中部中我使用了Element的第二种布局方式

<el-row>
    <el-col :span="19">主体数据</el-col>
    <el-col :span="5">侧边栏</el-col>
</el-row>

数据切换导航使用了Element中的<el-tabs>和<el-tab-pane>标签来达到中部数据的切换

<el-tab-pane label="最新" name="first">
<!--最新的歌曲卡片-->
<el-tab-pane>
<el-tab-pane label="推荐" name="second">
<!--推荐的歌曲卡片-->
</el-tab-pane>
歌曲卡片

使用Element的标签组件使用v-for加载多张卡片数据

<el-space wrap>
    <el-card :body-style="{ padding: '0px' }" v-for="(i,j) in name.length" :key="j" class="box-card"
 v-loading="loading">
       <div>
           <!--卡片数据-->
       </div>
    </el-card>
</el-space>
用户卡片、排行榜

用户卡片和排行榜我单独写了一个组件将它布局到侧边栏中,其中使用aioxs向Django后端发送请求来获取其中的数据

<!--在template中使用-->
<CariyselMin/>
<Latest/>
<!--在script中导入组件-->
import CariyselMin from "@/components/cariyselMin.vue";
import Latest from "@/components/latest.vue";

<!--在export default中注册组件-->
components: {CariyselMin, Latest},
回到顶部的按钮

使用Element中的标签组件,写入距离网页右边的距离,距离底部的距离

<el-backtop :right="15" :bottom="100"/><!--回顶部按钮-->
Vue+Django音乐网站
Vue+Django音乐网站

音乐播放器

图标使用Element中的标签组件el-icon图标

对音乐盒的各个控件使用vue的事件绑定

播放方法

当我们点击播放按钮时执行该方法,对定义的this.startPause变量进行判断,是否为'iconfont icon-bofang'字符串,是则播放,不是则停止

接着使用三元运算符来对this.startPause变量进行判断这个方法可以根据当前按钮的状态对this.startPause变量进行赋值,

startPauseDemo()
{
  if (this.startPause === 'iconfont icon-bofang') {
    this.$refs.music.play()
    console.log('播放')
  } else {
    this.$refs.music.pause()
    console.log('暂停')
  }
  this.startPause === 'iconfont icon-bofang' ? this.startPause = 'iconfont icon-zanting1' : this.startPause = 'iconfont icon-bofang'
}
,
切歌方法

判断当前歌曲数据的最大长度,判断是否还有下一首歌

如果有则让数据的下标this.i变量移动一位,停止播放,让播放图标变为暂停状态

next()
{
  if (this.i < this.musicSrc.length - 1) {
    this.i++
    this.timeLine = 0
    this.$refs.music.pause()
    this.startPause = 'iconfont icon-bofang'
  }
}
},
音量调节

使用vue的watch监控音量变量,当volume音量变量发生改变时返回发生改变前旧的值和发生改变以后新的值,使用DOM将值赋值到<audio>标签的音量变量上

watch: {
  volume(oldValue, newValue)
  {
    this.$refs.music.volume = newValue/*音量*/
  }
,
}
Vue+Django音乐网站
Vue+Django音乐网站
Vue+Django音乐网站

axios数据请求

此处代码使用axios对我们的Django后端服务器中的127.0.0.1:8000/getMuscic/链接发起了get请求

携带参数?=getMusic

使用then..catch错误捕获的方法进行请求,

如果请求成功就会返回正常的res请求数据,反之则会返回错误的error报错信息

我们的数据在res的data属性中将data中的数据添加到变量当中

jpg和mp3数据使用beast64编码进行传输在img的src属性中需要添加前缀

data:image/png;base64,

data:audio/mp3;base64,

使数据能正确的分析

    getData() {
      console.log('请求音乐盒数据')
      axios.get('/api/getMusic/', {
        params: {
          request: 'getMusic',
        },
      }).then((res) => {
        console.log(res)
        console.log('获取音乐')
        console.log(res.data)
        var data = res.data
        for (var i = 0; i <= data.length; i++) {
          this.name[i] = data[i].name;
          this.musician[i] = data[i].singer;
          this.musicSrc[i] = 'data:audio/mp3;base64,' + data[i].file;
          const base64Data = data[i].cover;
          this.imgSrc[i] = 'data:image/png;base64,' + base64Data;

          this.duration[i] = data[i].time;
        }
      console.log(this.banner)
    }
).catch(error => {
  // 处理错误
  console.log('请求数据未成功')
  console.log(error)
})
},

启动前端服务

 npm run dev
- THE END -

dajavv

4月20日12:44

最后修改:2024年4月20日
0

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论