文件上传和表单提交的前后端构建

文件上传和表单提交的前后端构建

前端的结构

  • 定义一个upload组件
  • 在父组件中引入三个upload组件
  • 在父组件中用ref获取每一个upload组件
  • 每个upload组件中定义fileList 和uploadFlag属性,并暴露(defineExpose)
  • upload中使用:http-request=自定义上传函数(后端有对应view),书写上传逻辑
  • 上传成功后fileList等一些列属性收集到值
  • 在父组件中点击submit,收集表单数据,包括name、type、date和三个upload组件中的fileList值,并发起提交请求。
  • 当然在api文件夹中封装请求,在组件中直接调用请求即可

upload组件

使用Element-Plus的组件upload https://element-plus.org/zh-CN/component/upload.html

upload中的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<template>
<!-- upload组件 -->
<!-- action字段中的值是后端提供的 -->
<!-- :http-request="uploadData",其中uploadData是自定义的上传函数 -->
<el-upload
v-model:fileList="fileList"
accept=".zip"
class="divBoxofUpload"
action="http://127.0.0.1:8000/upload"
multiple
:show-file-list="true"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:limit="1"
:on-exceed="handleExceed"
:on-change="handleChange"
:on-success="handleSuccess"
:http-request="uploadData"
>
<el-button size="default" type="primary" style="margin-right:10px;">点击上传压缩包</el-button>
</el-upload>
</template>

<script setup lang="ts">
import { ref, toRaw } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import{ reqUploadFile } from '@/api/map/index'
import type { UploadProps } from 'element-plus'

let fileList = ref<any[]>([])
let uploadFlag = ref(false)
const handleRemove: UploadProps['onRemove'] = (file, uploadFiles) => {
console.log(file, uploadFiles)
}
// 自定义上传函数的处理逻辑,将请求后返回的文件url,和文件name赋值给fileList,再由组件传给其父组件。
const uploadData: UploadProps['httpRequest'] = async(uploadData) => {
const res = await reqUploadFile(uploadData)
if (res.code === 200) {
console.log(res.data.name,res.data.file_url)
fileList.value = [{name:res.data.name , url:res.data.file_url}]

console.log('fileList',toRaw(fileList.value))
uploadFlag.value = true
} else {
ElMessage.error('Upload failed')
}
}
//暴露出父组件要访问的两个字段。
defineExpose({
fileList,
uploadFlag
})
</script>

<style scoped>
.divBoxofUpload {
padding-left: 10px;
display: flex;
align-items: center;
}
</style>

upload的父组件中的逻辑

upload的父组件中有一个表单,也就是场景为1时的el-card

  • 用于收集信息的表单name、type、date、和三个upload组件
  • 用于提交数据的submit按钮,取消提交的取消按钮
  • submit点击后发起表单提交的请求

表单提交的逻辑:收集信息,将表单中的name、type、date、和三个upload组件的fileList收集起来,提交给后端。后端返回200表示提交成功后,清空表单数据,切换场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
<template>
<el-card v-if="scene === 0" class="dataShow">
<el-button type="primary" size="default" icon="Plus" style="margin-bottom:10px" @click="addData">添加数据</el-button>
<el-button type="primary" size="default" icon="Plus" style="margin-bottom:10px" @click="scene=2">抽屉</el-button>
<h3>数据列表</h3>
<el-table stripe>
<el-table-column prop="name" label="异常名称" width="180"></el-table-column>
<el-table-column prop="type" label="异常类型" width="180"></el-table-column>
<el-table-column prop="date" label="发生时间"></el-table-column>
<el-table-column prop="operator" label="操作">
<el-button icon="Monitor">综合</el-button>
<el-button icon="Delete">删除</el-button>
</el-table-column>
</el-table>
</el-card>
<el-card v-if="scene === 1" class="dataLoad">
<el-form label-width="100px">
<el-form-item label="异常名称">
<el-input v-model="dataUpLoadVar.name"></el-input>
</el-form-item>
<el-form-item label="异常类型" >
<el-select v-model="dataUpLoadVar.type">
<el-option label="火灾" value="fire"></el-option>
<el-option label="地震" value="earthquake"></el-option>
<el-option label="水华" value="algae_bloom"></el-option>
<el-option label="滑坡" value="landslide"></el-option>
<el-option label="其他" value="other"></el-option>
</el-select>
</el-form-item>
<el-form-item label="发生时间" >
<el-input type="date" v-model="dataUpLoadVar.date"></el-input>
</el-form-item>
<el-form-item label="异常强度数据" >
<div class="uploadDiv">
<upload ref="loadIntensity" ></upload>
</div>
</el-form-item>
<el-form-item label="异常影响数据" >
<div class="uploadDiv">
<upload ref="loadInfluence" ></upload>
</div>
</el-form-item>
<el-form-item label="异常属性数据" >
<div class="uploadDiv">
<upload ref="loadProperty" ></upload>
</div>
</el-form-item>
</el-form>
<div class="btnPosition">
<el-button type="primary" size="default" @click="cancel">取消</el-button>
<el-button type="primary" size="default" @click="submitData">提交</el-button>
</div>
</el-card>
<!-- ref="mainCRef" 控制子组件 -->
<mainC v-if="scene === 2" @changemainScene="changeSceneTo"></mainC>
</template>

<script setup lang="ts">
import {ref, provide} from 'vue'
import {reqSubmitData} from '@/api/map/index'
import upload from './upload/index.vue'
import mainC from '@/view/mainC/index.vue'
import { ElMessage } from 'element-plus';
let scene = ref(0)
// 父传给子的属性
// let intensityFileList:Array<any> =[];
// let impactFileList:Array<any> = [];
// let attributeFileList:Array<any> = [];
provide('pscene', scene)
//数据上传页面所需变量
let dataUpLoadVar = ref<any>({
name:'',
type:'',
date:'',
intensity:'',
influence:'',
property:''
})
// 获取子组件的实例
const loadIntensity = ref()
const loadInfluence = ref()
const loadProperty = ref()
let mainCRef = ref()
const addData = ()=>{
scene.value = 1
console.log('添加数据')
//导入本地数据

}
// 提交函数
const submitData = () => {
console.log('提交数据')
const formData = new FormData()
formData.append('name', dataUpLoadVar.value.name)
formData.append('type', dataUpLoadVar.value.type)
formData.append('date', dataUpLoadVar.value.date)
// console.log(loadIntensity.value.fileList[0].url)
// console.log(loadInfluence.value.fileList[0].url)
// console.log(loadProperty.value.fileList[0].url)
formData.append('intensity_file',loadIntensity.value.fileList[0].url)
formData.append('impact_file', loadInfluence.value.fileList[0].url)
formData.append('attribute_file', loadProperty.value.fileList[0].url)
// 不能直接console.log(FormData),需要遍历
// 打印 FormData 对象的内容
for (let [key, value] of formData.entries()) {
console.log(key, value)
}

// 提交数据
reqSubmitData(formData).then(res => {
console.log(res)
if (res.code === 200) {
console.log('提交成功')
} else {
console.log('提交失败')
ElMessage.error('提交失败')
}
})
// 清空数据
dataUpLoadVar = Object.assign(dataUpLoadVar.value, {
name: '',
type: '',
date: '',
// ...
})

//切换场景
scene.value = 0
}

const cancel = ()=>{
console.log('取消')
// 清空数据
dataUpLoadVar = Object.assign(dataUpLoadVar.value,{
name:'',
type:'',
date:'',
intensity:'',
influence:'',
property:''
})
//切换场景
scene.value = 0
}
const changeSceneTo = (num: number)=>{
scene.value = num
}
</script>

后端结构

Django

  • models.py:这个文件定义了你的数据模型。在 Django 中,一个模型代表了数据库中的一个表,模型的一个实例代表了表中的一行。模型中的每个字段都是数据库表中的一个列。
  • views.py:这个文件定义了你的视图。在 Django 中,一个视图是一个 Python 函数,它接收一个 Web 请求并返回一个 Web 响应。这个响应可以是 HTML 的内容、重定向或者 404 错误等。
  • forms.py:这个文件定义了你的表单。在 Django 中,表单是用来收集和处理用户输入的一种工具。表单可以生成 HTML 表单的代码,验证和清理用户提交的数据。
  • urls.py:这个文件定义了你的 URL 配置。在 Django 中,URL 配置是一个 URL 模式和一个视图函数之间的映射。当 Django 收到一个请求时,它会根据请求的 URL,查找匹配的 URL 模式,然后调用相应的视图函数。

models

定义两个models,也就是定义两个表,分别是uploadFile和AnomalyData

uploadFile用于保存上传的每个文件的url,AnomalyData用于保存每次提交后的表单数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from django.db import models
import uuid

def generate_unique_name():
return str(uuid.uuid4())

class upLoadFile(models.Model):
# name = models.CharField(max_length=255, default=generate_unique_name) # 文件名
file_path = models.FileField(upload_to='uploads/') # 文件路径
# size = models.IntegerField() # 文件大小
class AnomalyData(models.Model):
# upload_to 可以是一个字符串,也可以是一个函数。如果它是一个字符串,
# 那么这个字符串会被添加到你的 MEDIA_ROOT 设置的路径后面,来形成文件的完整路径。
# 例如,如果你的 MEDIA_ROOT 是 /var/www/mywebsite/media/,并且 upload_to 是 'uploads/intensity/',
# 那么上传的文件会被保存到 /var/www/mywebsite/media/uploads/intensity/ 目录。
intensity_file = models.FileField(upload_to='submit/intensity/')
impact_file = models.FileField(upload_to='submit/impact/')
attribute_file = models.FileField(upload_to='submit/attribute/')
name = models.CharField(max_length=200)
type = models.CharField(max_length=200)
date = models.DateField()

form

创建表单数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from django import forms
class SubmitForm(forms.Form):
name = forms.CharField(label='异常名称')
type = forms.ChoiceField(label='异常类型', choices=[
('fire', '火灾'),
('earthquake', '地震'),
('algae_bloom', '水华'),
('landslide', '滑坡'),
('other', '其他'),
])
date = forms.DateField(label='发生时间')
density_file = forms.FileField(label='异常强度数据')
impact_file = forms.FileField(label='异常影响数据')
attribute_file = forms.FileField(label='异常属性数据')


# 上传文件的表单
class UploadFileForm(forms.Form):
file = forms.FileField(label='文件')

views

  • 实现上传文件和提交表单的视图

    上传文件后,返回文件再文件系统中的url和名称

提交表单数据后,将数据保存到AnomalyData表中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
from django.core.files.storage import FileSystemStorage
from .models import AnomalyData, upLoadFile

# 上传文件
@csrf_exempt
def upload_file(request):
if request.method == 'POST':
uploaded_file = request.FILES['file']
# 指定了上传文件的保存路径
fs = FileSystemStorage('uploads/')
name = fs.save(uploaded_file.name, uploaded_file)
# 生成url
file_url = fs.url(name)

# 保存文件路径到数据库
upLoad_data = upLoadFile(file_path=file_url)
upLoad_data.save()

# 成功后返回必要的code,status等字段,还需返回文件的名称和file_url
return JsonResponse({
'status': 'success',
'code': 200,
'data': {
'file_url': file_url,
'name': name
}
})

return JsonResponse({
'code': 400,
'error': 'Invalid method',
'status': 'failed'
})

# 提交表单
@csrf_exempt
def submit_form(request):
# 前端提供一个FormData,从FormData中获取需要的字段值,使用request.POST.get('字段名')
if request.method == 'POST':
name = request.POST.get('name')
type = request.POST.get('type')
date = request.POST.get('date')
intensity_file = request.POST.get('intensity_file')
impact_file = request.POST.get('impact_file')
attribute_file = request.POST.get('attribute_file')
print(name, type, date, intensity_file, impact_file, attribute_file)
# 如果有一个是空的,返回提交失败
if not all([name, type, date, intensity_file, impact_file, attribute_file]):
return JsonResponse({'status': 'fail', 'code': 400, 'message': 'Missing required fields'})
# 如果表单中的所有元素都有值,保存到数据库中的AnomalyData表中
# anomaly_data是一个AnomalyData实例,每个实例代表表中的一行记录
anomaly_data = AnomalyData(name=name, type=type, date=date, intensity_file=intensity_file, impact_file=impact_file, attribute_file=attribute_file)
anomaly_data.save()
return JsonResponse({'status': 'success', 'code': 200})

urls

定义上传文件和提交表单信息的url

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.contrib import admin
from django.urls import re_path
from django.urls import path
from . import views

urlpatterns = [
re_path(r'^admin/?$', admin.site.urls),
re_path(r'^register/?$', views.register , name='register'),
re_path(r'^login/?$', views.login_view, name='login'),
re_path(r'^logout/?$', views.logout_view, name='logout'),
re_path(r'^upload/?$', views.upload_file, name='upload'),
re_path(r'^submit/?$', views.submit_form, name='submit'),
]