DRF学习——VUE+DRF实现用户头像上传

目录

一、简介

二、DRF

①Settings文件配置

②Urls文件配置

③Models文件

④Views文件

三、VUE

①router

②views


一、简介

本篇文章主要用于介绍如何使用VUE+DRF实现用户头像上传

二、DRF

①Settings文件配置

在DRF框架下如果想要实现文件的上传,需要配置媒体文件的目录与路由,具体配置如下:

# zero_demo/settings.py
# 媒体文件目录
from pathlib import Path
import os

BASE_DIR = Path(__file__).resolve().parent.parent
MEDIA_ROOT = os.path.join(BASE_DIR, "media")
# 上传文件目录
UPLOAD_PATH = 'upload/'
# 访问媒体文件的路由
MEDIA_URL = '/media/'

②Urls文件配置

settings文件最后一行代码配置了访问媒体文件的路由,这是不够的,还需要在urls文件中配置具体的路由,其配置如下:

# zero_demo/urls.py
from django.urls import re_path
from django.views.static import serve
from django.conf import settings

urlpatterns = [
    re_path(r"^media/(?P<path>.*)$", serve, {'document_root': settings.MEDIA_ROOT}, name='media'),
]

③Models文件

配置好settings和urls文件之后,在数据库中创建一张用户表,用于存储与用户有关的所有信息,models文件代码如下:

class Admin(models.Model):
    """ 用户表 """
    user = models.CharField(verbose_name="用户名", max_length=32)
    password = models.CharField(verbose_name="密码", max_length=32)
    head = models.CharField(verbose_name="头像路径", max_length=128)

④Views文件

最后则是在Views文件中实现上传头像的逻辑,大致是思路是编写一个API接口提供给前端VUE界面发送POST请求,然后将上传的图片存储与media目录下,接着返回存储与media目录下的url给前端,以供前端通过url找到存储在media目录下的图片进行展示 ,具体代码实现如下:

序列化器

# apps/rbac/views.py
class AdminSerializer(serializers.ModelSerializer):
    role_display = AdminRoleSerializer(many=True, source='roles', read_only=True)
    # 使用自定义钩子方法,返回带前缀的url
    head_url = serializers.SerializerMethodField()

    class Meta:
        model = models.Admin
        fields = "__all__"

    def get_head_url(self, obj):
        # 调用request中的build_absolute_uri方法,返回带前缀的url
        return self.context['request'].build_absolute_uri(obj.head)

View类

这里调用了@action方法,它的作用可以让在调用AdminView的url的后面增加新的url,例如/api/rbac/admin/这个url调用了AdminView,它的后面会多增加一个/api/rbac/admin/upload/的url

from rest_framework.decorators import action
class AdminView(ModelViewSet):
    authentication_classes = []
    permission_classes = []
    queryset = models.Admin.objects.all()
    serializer_class = AdminSerializer

    def get_serializer_class(self):
        if self.request.method == "POST":
            return AdminCreateSerializer
        return AdminSerializer

    # detail=False的意思是生成的额外url不用带id
    @action(detail=False, methods=['post'], url_path="upload")
    def upload(self, request):
        upload_object = request.FILES.get("file")
        # upload_object.size # 文件大小
        # upload_object.name # 文件名称
        # upload_object.read() # 读取整个文件
        # for chunk in upload_object.chunks(1024):    # 每次读取1024字节的文件
        #     pass # 读取内容写入到本地

        if upload_object.size > 1 * 1024 * 1024:
            return Response({
                "code": 444,
                "msg": "文件太大"
            })
        upload_url = get_upload_filename(upload_object.name)
        save_path = default_storage.save(upload_url, upload_object)
        local_url = default_storage.url(save_path)
        abs_url = request.build_absolute_uri(local_url)

        context = {
            'data': {
                'url': local_url,
                'abs_url': abs_url
            }
        }

        # 返回本地路径
        return Response(context)

上面的代码中调用了get_upload_filename方法

upload_url = get_upload_filename(upload_object.name)

其定义如下:

# apps/rbac/views.py

from datetime import datetime
from django.conf import settings
import os
from django.core.files.storage import default_storage

def get_upload_filename(file_name):
    date_path = datetime.now().strftime('%Y/%m/%d')
    upload_path = os.path.join(settings.UPLOAD_PATH, date_path)
    file_path = os.path.join(upload_path, file_name)
    return default_storage.get_available_name(file_path)

将Views文件配置完成后,后端也就算准备完成,接下来编写前端VUE代码

三、VUE

①router

首先在router注册一个用户编辑的路由

# router/index.js
const routes = [
    {
        path: '/authEdit',
        name: 'AuthEdit',
        component: () => import('../views/account/AuthEditView.vue')
    },
]

②views

路由中调用了AuthEditView.vue,因此需要在编写该vue,即用户编辑的页面

<template>
    <el-card class="box-card" shadow="never">
        <template #header>
            <div class="card-header" style="display: flex;justify-content: space-between;align-items: center;">
                <div>
                    <span style="font-weight: bold;font-size: 18px;">个人信息编辑</span>
                </div>
                <router-link :to="{name:'ip156'}" style="text-decoration: none">
                    <el-button type="primary">返回</el-button>
                </router-link>
            </div>
        </template>
        <el-form :model="state.form" ref="formRef" label-width="140px">
            <div style="padding: 0 10px;">
                <div>
                    <h4>个人信息</h4>

                    <el-row :gutter="30">
                        <el-col :span="12">
                            <el-form-item style="margin-top: 24px;" :error="state.error.user" label="邮箱前缀">
                                <el-input v-model="state.form.user" placeholder="邮箱前缀"></el-input>
                            </el-form-item>
                        </el-col>
                        <el-col :span="12">
                            <el-form-item style="margin-top: 24px;" :error="state.error.password" label="账户密码">
                                <el-input v-model="state.form.password" placeholder="账户密码"></el-input>
                            </el-form-item>
                        </el-col>
                    </el-row>
                    <el-row :gutter="30">
                        <el-col :span="12">
                            <el-form-item style="margin-top: 24px;" :error="state.error.head" label="个人头像">

                                <ul v-if="state.form.head" class="el-upload-list el-upload-list--picture-card"
                                    style="width: 200px;height: 150px;">
                                    <li class="el-upload-list__item is-success" style="width: 200px;height: 120px;">
                                        <img class="el-upload-list__item-thumbnail" :src="state.form.head_url">

                                        <span class="el-upload-list__item-actions">
                                            <span class="el-upload-list__item-preview"
                                                  @click="state.dialogLicenceVisible=true">
                                                <el-icon><ZoomIn/></el-icon>
                                            </span>
                                            <span class="el-upload-list__item-delete" @click="removeLicenceImage">
                                                <el-icon><Delete/></el-icon>
                                            </span>
                                        </span>
                                    </li>
                                </ul>

                                <el-upload
                                        v-else
                                        style="width: 200px;height: 150px;"
                                        drag
                                        :data="{type:'head'}"
                                        :show-file-list="false"
                                        :multiple="false"
                                        :action="imageUploadUrl"
                                        :before-upload="beforeImageUpload"
                                        :on-success="uploadSuccessWrapper('head','head_url')">
                                    <el-icon class="el-icon--upload">
                                        <upload-filled/>
                                    </el-icon>
                                    <template #tip>
                                        <div class="el-upload__tip"
                                             style="    text-align: center;font-size: 8px;margin-top: 0;line-height: 25px;">
                                            只能上传图片文件,且不超过2M
                                        </div>
                                    </template>
                                </el-upload>

                            </el-form-item>
                        </el-col>

                    </el-row>

                </div>
                <el-divider border-style="dotted"/>
                <el-row justify="center" align="middle" style="height: 80px;">
                    <el-button type="primary" style="width: 200px;height: 40px;" @click="doSubmit">提交修改</el-button>
                </el-row>

            </div>
        </el-form>
    </el-card>

    <el-dialog v-model="state.dialogLicenceVisible">
        <img w-full :src="state.form.head_url" alt="Preview Image" style="width: 55%;height: 100%"/>
    </el-dialog>
</template>

<script setup>
import {reactive, onMounted, getCurrentInstance} from 'vue'
import {UploadFilled, Delete, ZoomIn} from '@element-plus/icons-vue'
import {ElMessage} from 'element-plus'
import {useStore} from "vuex";
import {clearFormError} from "@/plugins/form";

const store = useStore()
const {proxy} = getCurrentInstance()

// 上传图片
const imageUploadUrl = "http://127.0.0.1:8000/api/rbac/admin/upload/"

const state = reactive({
    dialogLicenceVisible: false,
    form: {
        user: "",
        password: "",
        head: "",
        head_url: "",
    },
    // preview:{
    //     head_url: "",
    // },
    error: {
        user: "",
        password: "",
        head: "",
    },
})


function beforeImageUpload(file) {
    const isPNG = file.type === 'image/png';
    const isLt2M = file.size / 1024 / 1024 < 2;
    if (!isPNG) {
        ElMessage.error("上传图片只能是图片格式!");
    }
    if (!isLt2M) {
        ElMessage.error("上传图片大小不能超过 2MB!");
    }
    return isPNG && isLt2M;
}


function uploadSuccessWrapper(fieldName, preViewFieldName) {
    return function (res) {
        if (res.code === 0) {
            // 1.图片地址+返回时添加
            // 2.服务器支持访问静态图片
            // {"code":0,"data":{"url":"/media/uploads/2022/04/01/2_nO3mqV7.jpeg","abs_url":".htt"}}
            state.form[fieldName] = res.data.data.url;
            state.form[preViewFieldName] = res.data.data.abs_url;
        } else {
            ElMessage.error('上传失败:' + res.msg);
        }
    }
}


function removeLicenceImage() {
    state.form.head_url = ""
    state.form.head = ""
}


onMounted(() => {
    initAccount()
})


function initAccount() {
    let id = store.state.id;
    proxy.$axios.get(`/api/rbac/admin/${id}/`).then((res) => {
        if (res.data.code === 0) {
            state.form = res.data.data
        } else {
            ElMessage.error('数据获取失败')
        }
    })
}


function doSubmit() {
    //1.表单校验
    clearFormError(state.error)

    //2.发送请求
    let id = store.state.id;
    proxy.$axios.patch(`/api/rbac/admin/${id}/`, state.form).then((res) => {
        ElMessage.success("修改成功")
    })
}

</script>

<style scoped>
.avatar-uploader .avatar {
    width: 200px;
    height: 120px;
    display: block;
}
</style>
<style>
.el-upload-dragger {
    padding: 20px;
}

.avatar-uploader .el-upload {
    border: 1px dashed var(--el-border-color);
    border-radius: 6px;
    cursor: pointer;
    position: relative;
    overflow: hidden;
    transition: var(--el-transition-duration-fast);
}

.avatar-uploader .el-upload:hover {
    border-color: var(--el-color-primary);
}

.el-icon.avatar-uploader-icon {
    font-size: 28px;
    color: #8c939d;
    width: 200px;
    height: 120px;
    text-align: center;
}
</style>

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个使用 Django Rest Framework (DRF)、Vue 3 和 Element Plus 搭建的前后端分离模板。 ### 后端 (Django Rest Framework) 1. 创建一个 Django 项目和应用程序: ``` $ django-admin startproject myproject $ cd myproject $ python manage.py startapp myapp ``` 2. 安装 DRF: ``` $ pip install djangorestframework ``` 3. 在 `myproject/settings.py` 中添加 DRF 和 CORS 的配置: ```python INSTALLED_APPS = [ # ... 'rest_framework', ] MIDDLEWARE = [ # ... 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', ] REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.SessionAuthentication', ), 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', ), } CORS_ORIGIN_ALLOW_ALL = True ``` 4. 在 `myapp/views.py` 中定义一个视图: ```python from rest_framework.decorators import api_view from rest_framework.response import Response @api_view(['GET']) def hello(request): return Response({'message': 'Hello, world!'}) ``` 5. 在 `myproject/urls.py` 中添加路由: ```python from django.urls import path, include from myapp.views import hello urlpatterns = [ path('api/', include([ path('hello/', hello), ])), ] ``` 6. 运行服务器: ``` $ python manage.py runserver ``` 访问 `http://localhost:8000/api/hello/` 应该会返回 `{"message": "Hello, world!"}`。 ### 前端 (Vue 3 + Element Plus) 1. 使用 Vue CLI 创建一个新项目: ``` $ vue create myproject-frontend ``` 2. 安装 Element Plus: ``` $ npm install element-plus --save ``` 3. 在 `main.js` 中引入 Element Plus 和样式: ```javascript import { createApp } from 'vue' import App from './App.vue' import ElementPlus from 'element-plus' import 'element-plus/lib/theme-chalk/index.css' createApp(App) .use(ElementPlus) .mount('#app') ``` 4. 在 `App.vue` 中添加一个按钮和一个文本框: ```vue <template> <div class="container"> <el-input v-model="name" placeholder="Enter your name"></el-input> <el-button type="primary" @click="sayHello">Say hello</el-button> <div class="result">{{ result }}</div> </div> </template> <script> import axios from 'axios' export default { name: 'App', data() { return { name: '', result: '' } }, methods: { sayHello() { axios.get('/api/hello/', { headers: { 'Authorization': 'Token ' + sessionStorage.getItem('token') } }) .then(response => { this.result = response.data.message }) .catch(error => { console.error(error) }) } } } </script> <style> .container { max-width: 800px; margin: 0 auto; padding: 20px; } .result { margin-top: 20px; font-weight: bold; } </style> ``` 5. 运行服务器: ``` $ npm run serve ``` 访问 `http://localhost:8080/` 应该会显示一个文本框和一个按钮。在文本框中输入你的名字,然后点击按钮,应该会显示 `Hello, world!`。 这是一个简单的模板,你可以根据自己的需要进行扩展和定制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hemameba

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值