前言: 网上Django项目要么太老,要么不全。正好手里有个需求,借这个机会用django3.2加vue3(TS)一步步完整搭建一个图纸管理系统。一方面是记录,另一方面也回馈开源精神。希望和大家一起学习一起进步。本篇为前后端交互(最近更新8.2)
经过我们前期的努力,已经完成了后端前端的编写。那么接下来就是进行积木搭建的最后一步了!前后端交互!
三、前后端交互
1、前端跨域获取后端接口数据
这里简单说下这个流程大概是什么样子的:
a:前端如何发送获取数据的请求
axios-->Ajax
前端访问地址:http://localhost:8080/
b:后端接受请求,去数据库查询数据,返回json数据给前端
DRF --> 接口
前端访问地址:http://localhost:8000
c:前端把数据展示在页面中去
那什么是跨域呢?
前端当前的地址和后端访问的地址有三个中有一个不一样,就叫跨域
a.协议:http[都一样]
b.IP地址192.168.0.1 192.168.0.2 [不一样]
c.端口8080 8000 [不一样]
1.1 跨域获取所有图纸信息
先安装axios包:npm install axios@0.26.1
如何直接运行,会发现拿不到数据,因为我们没有对跨域进行处理
先在setting.py中加入
ALLOWED_HOSTS = ['*',]
然后在manage.py中加入
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'TestPlatform.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == "__main__":
from django.core.management.commands.runserver import Command as Runserver
Runserver.default_addr = 'localhost' # 修改默认地址
Runserver.default_port = '8000' # 修改默认端口
main()
这样可以保证后端运行都在localhost:8000
我们需要安装一个插件完成跨域
pip install django-cors-headers==3.11.0
安装好后配置:
最后在settings.py中加入
# ============跨域请求=============
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = (
'http://localhost:8080',
)
运行后会发现可以拿到后端数据了
进行代码优化,把获取的数据放到前端页面上
效果为:
为了友好型,如果加载车成功给一个提示语句
导入
// 导入ElementPlus
import { ElMessage,ElMessageBox } from 'element-plus'
1.2 优化信息展示
对现实数据条数优化
我们发现拿到的数据现实属于什么传感器,属于什么项目都是id不是我们要的文字,对此我们需要这样处理:
'''
@Project :gveInformationSystemBE
@File :serializer.py
@IDE :PyCharm
@Author :HXW
@Date :2023/5/23 15:48
'''
# ===========
from rest_framework import serializers
from DrawingManagementSystem.models import Sensor, Project, Data, Drawing
# ----Sensor序列化类----
class SensorSerialzer(serializers.ModelSerializer):
class Meta:
model = Sensor
fields = "__all__"
# ____Project序列化类____
class ProjectSerialzer(serializers.ModelSerializer):
sensor = SensorSerialzer()
class Meta:
model = Project
fields = "__all__"
# ____Data序列化类____
class DataSerialzer(serializers.ModelSerializer):
project = ProjectSerialzer()
class Meta:
model = Data
fields = "__all__"
# ____Drawing序列化类____
class DrawingSerialzer(serializers.ModelSerializer):
data = DataSerialzer()
class Meta:
model = Drawing
fields = "__all__"
效果为:
表格过长呢?
只需要在后面加:show-overflow-tooltip="true"
<!-- 2.表格部分 -->
<el-table :data="Data.drawing" border style="width: 100%" :header-cell-style="{ backgroundColor:'#409EFF',color:'#FFF',fontSize:'14px' }">
<el-table-column label="序号" type="index" align="center" width="60" />
<el-table-column prop="material_code" label="物料编号" align="center" width="180" :show-overflow-tooltip="true"/>
<el-table-column prop="drawing_name" label="材料名称" align="center" width="160" />
<el-table-column prop="drawing_spec" label="规格/图纸号" align="center" width="180" :show-overflow-tooltip="true"/>
<el-table-column prop="drawing_page" label="图纸页数" align="center" width="60" />
<el-table-column prop="drawing_client_id" label="客户编号" align="center" width="180" :show-overflow-tooltip="true"/>
<el-table-column prop="drawing_version" label="版本号" align="center" width="70" />
<el-table-column prop="drawing_remark" label="备注" align="center" width="180" :show-overflow-tooltip="true"/>
<el-table-column prop="modified" label="最后上传日期" align="center" width="160" />
<el-table-column label="操作" align="center">
<el-button type="primary" :icon="More" circle size="small"/>
<el-button type="warning" :icon="Edit" circle size="small"/>
<el-button type="danger" :icon="Delete" circle size="small"/>
</el-table-column>
</el-table>
2、axios请求优化(一)
如果每次调用接口都要输入一堆实在是太麻烦了,这里可以进行简化
在src根目录新建api文件如下:
新建utils文化如下:
src\utils\request.ts
// 导入
import axios from "axios";
// 创建一个Axios的app
const request = axios.create({
baseURL:'http://localhost:8000/DrawingApi/v1/',
timeout:5000
})
// 请求拦截器
// 响应拦截器
// 暴露
export default request
src\api\sensors.ts
// 导入
import request from "../utils/request";
// RESTful 有(获取所有,添加,获取某一个,修改,删除)
// 获取所有
const getAll = (params:any) => {
// request
return request({
method:'get',
url:'Sensors/',
params
})
}
// 添加
const add = (data:any) => {
// request
return request({
method:'post',
url:'Sensors/',
data
})
}
// 获取单一数据
const getOne = (id:any) => {
// request
return request({
method:'get',
url:'Sensors/${id}/',
})
}
// 修改
const edit = (id:any,data:any) => {
// request
return request({
method:'put',
url:'Sensors/${id}/',
data
})
}
// 删除
const del = (id:any,data:any) => {
// request
return request({
method:'delete',
url:'Sensors/${id}/',
})
}
// 暴露所有
export default{
getAll,getOne,edit,add,del
}
src\api\projects.ts
// 导入
import request from "../utils/request";
// RESTful 有(获取所有,添加,获取某一个,修改,删除)
// 获取所有
const getAll = (params:any) => {
// request
return request({
method:'get',
url:'Projects/',
params
})
}
// 添加
const add = (data:any) => {
// request
return request({
method:'post',
url:'Projects/',
data
})
}
// 获取单一数据
const getOne = (id:any) => {
// request
return request({
method:'get',
url:'Projects/${id}/',
})
}
// 修改
const edit = (id:any,data:any) => {
// request
return request({
method:'put',
url:'Projects/${id}/',
data
})
}
// 删除
const del = (id:any,data:any) => {
// request
return request({
method:'delete',
url:'Projects/${id}/',
})
}
// 暴露所有
export default{
getAll,getOne,edit,add,del
}
src\api\datas.ts
// 导入
import request from "../utils/request";
// RESTful 有(获取所有,添加,获取某一个,修改,删除)
// 获取所有
const getAll = (params:any) => {
// request
return request({
method:'get',
url:'Datas/',
params
})
}
// 添加
const add = (data:any) => {
// request
return request({
method:'post',
url:'Datas/',
data
})
}
// 获取单一数据
const getOne = (id:any) => {
// request
return request({
method:'get',
url:'Datas/${id}/',
})
}
// 修改
const edit = (id:any,data:any) => {
// request
return request({
method:'put',
url:'Datas/${id}/',
data
})
}
// 删除
const del = (id:any,data:any) => {
// request
return request({
method:'delete',
url:'Datas/${id}/',
})
}
// 暴露所有
export default{
getAll,getOne,edit,add,del
}
src\api\drawings.ts
// 导入
import request from "../utils/request";
// RESTful 有(获取所有,添加,获取某一个,修改,删除)
// 获取所有
const getAll = (params:any) => {
// request
return request({
method:'get',
url:'Drawings/',
params
})
}
// 添加
const add = (data:any) => {
// request
return request({
method:'post',
url:'Drawings/',
data
})
}
// 获取单一数据
const getOne = (id:any) => {
// request
return request({
method:'get',
url:'Drawings/${id}/',
})
}
// 修改
const edit = (id:any,data:any) => {
// request
return request({
method:'put',
url:'Drawings/${id}/',
data
})
}
// 删除
const del = (id:any,data:any) => {
// request
return request({
method:'delete',
url:'Drawings/${id}/',
})
}
// 暴露所有
export default{
getAll,getOne,edit,add,del
}
src\api\index.ts
// 导入所有api
import sensors from "./sensors";
import projects from "./projects";
import datas from "./datas";
import drawings from "./drawings";
// 封装暴露
export default{
sensors,projects,datas,drawings,
}
引用可为:
效果还是一样的:
3、把对应信息填充到下拉框
由于我们api都写好了,那么直接调用就好了:
script区域:
// 获取所有传感器的信息
const getSensors = () => {
return Api.sensors.getAll().then((res)=>{
Data.sensorOptions = res.data;
})
}
// 获取所有项目的信息
const getProjects = () => {
return Api.projects.getAll().then((res)=>{
Data.projectOptions = res.data;
})
}
// 获取所有资料的信息
const getDatas = () => {
return Api.datas.getAll().then((res)=>{
Data.dataOptions = res.data;
})
}
template 区域:
<!-- 1.查询区域 -->
<el-form :inline="true" class="demo-form-inline" style="display: flex">
<el-form-item label="查询条件:">
<el-input v-model="Data.q_str" placeholder="请输入查询条件" clearable/>
</el-form-item>
<el-form-item label="传感器:">
<el-select v-model="Data.sensorSelected" placeholder="请选择传感器" clearable filterable>
<el-option
v-for="item in Data.sensorOptions"
:key="item.id"
:label="item.sensor_name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="项目:">
<el-select v-model="Data.projectSelected" placeholder="请选择项目" clearable filterable>
<el-option
v-for="item in Data.projectOptions"
:key="item.id"
:label="item.project_name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="资料:">
<el-select v-model="Data.dataSelected" placeholder="请选择资料" clearable filterable>
<el-option
v-for="item in Data.dataOptions"
:key="item.id"
:label="item.data_name"
:value="item.id" />
</el-select>
</el-form-item>
对了 之前表格信息不全 进行补充
<!-- 2.表格部分 -->
<el-table :data="Data.drawing" border style="width: 100%" :header-cell-style="{ backgroundColor:'#409EFF',color:'#FFF',fontSize:'14px' }">
<el-table-column label="序号" type="index" align="center" width="60" />
<el-table-column prop="material_code" label="物料编号" align="center" width="180" :show-overflow-tooltip="true"/>
<el-table-column prop="drawing_name" label="材料名称" align="center" width="160" />
<el-table-column prop="data.project.sensor.sensor_name" label="所属传感器" align="center" width="80" :show-overflow-tooltip="true"/>
<el-table-column prop="data.project.project_name" label="所属项目" align="center" width="80" :show-overflow-tooltip="true"/>
<el-table-column prop="drawing_spec" label="规格/图纸号" align="center" width="180" :show-overflow-tooltip="true"/>
<el-table-column prop="drawing_page" label="图纸页数" align="center" width="60" />
<el-table-column prop="drawing_client_id" label="客户编号" align="center" width="100" :show-overflow-tooltip="true"/>
<el-table-column prop="drawing_version" label="版本号" align="center" width="70" />
<el-table-column prop="drawing_remark" label="备注" align="center" width="180" :show-overflow-tooltip="true"/>
<el-table-column prop="modified" label="最后上传日期" align="center" width="160" />
<el-table-column label="操作" align="center">
<el-button type="primary" :icon="More" circle size="small"/>
<el-button type="warning" :icon="Edit" circle size="small"/>
<el-button type="danger" :icon="Delete" circle size="small"/>
</el-table-column>
</el-table>
效果为:
4、对树进行数据填充
这边树的处理是前端拼接的,理论上由后端进行处理传给前端,前端按照层级点击后进行加载。但是这里由于项目是我一个人编写,且未来项目其实数据量不会很大。所以我这里还是以项目跑起来为主,后期再优化
// 将数据拼接成树
const getTree = () => {
Promise.all([getSensors(), getProjects(), getDatas()]).then(() => {
// 组合
for(let sensors of Data.sensorOptions){
// 定义需要的结构
var obj = reactive({
value:`${sensors.id}-${sensors.sensor_name}`,
label:sensors.sensor_name,
children:[],
depth: 1
})
// 遍历项目填充obj的children
for(let project of Data.projectOptions){
if(project.sensor.id === sensors.id){
var obj1 = reactive({
value:project.id,
label:project.project_name,
children:[],
depth: 2
});
for(let data of Data.dataOptions){
if(data.project.id === project.id){
obj1.children.push({
value:data.id,
label:data.data_name,
depth: 3
});
}
}
obj.children.push(obj1)
}
}
Data.treeData.push(obj)
}
});
}
// 定义页面加载的时候自动执行的函数
const autoRun = () => {
// 获取所有图纸信息
getData();
// 获取所有传感器的信息
getSensors();
getProjects();
getDatas();
getTree();
}
<!-- 侧边栏 -->
<div class="tree-container">
<el-tree :data="Data.treeData" @node-click="nodeClick"></el-tree>
</div>
效果为:
思考:这里如果通过树进行搜索的话,那我搜索的下拉框其实作用就不大了,且没有层级绑定。
这边暂时不进行修改,为了更好的展示项目知识点,方便今后其他项目需要进行修改。后期系统优化时候,再进行修改。
5、axios请求优化(二)
我们发现其实四个请求代码绝大部分代码是重复,可以进行优化
在api下新建一个apibase.ts
src\api\apibase.ts
// 导入
import request from "../utils/request";
// 面向对象 ---> 类
export default class APIBase{
// 定义属性
public name:string;
// 构造器
constructor(name:string){
this.name = name
}
// RESTFul方法
// 获取
public getAll = (params?:any) => {
return request({
method:'get',
url:`${this.name}/`,
params
})
}
// 添加
public add = (data:any) => {
// request
return request({
method:'post',
url:`${this.name}/`,
data
})
}
// 获取一个
public getOne = (id:any) => {
// request
return request({
method:'get',
url:`${this.name}/${id}`,
})
}
// 修改
public edit = (id:any,data:any) =>{
// request
return request({
method:'put',
url:`${this.name}/${id}`,
data
})
}
// 删除
public del = (id:any) =>{
// request
return request({
method:'delete',
url:`${this.name}/${id}`,
})
}
}
对src\api\index.ts进行修改
// 导入基础的类
import APIBase from "./apibase";
// 实例化对象
let sensors = new APIBase("Sensors")
let projects = new APIBase("Projects")
let datas = new APIBase("Datas")
let drawings = new APIBase("Drawings")
// 封装暴露
export default{
sensors,projects,datas,drawings,
}
这样就可以把旧的四个都删除了
有人问 那我要是添加新的api呢,那可以用继承啊
6、查询和显示全部
查询的流程为:
文本框接受input-》模糊匹配-》字段:id啥的
-》search【匹配多个字段】
select[传感器]-》匹配-》传感信息
select[项目]-》匹配-》项目信息
select[资料]-》匹配-》资料信息
注意:三个是并且的关系
已知我们当初设置的多查询其实是多个字段:
只需要在getData函数中加入一个字段就行
并给按钮赋值一个点击函数
就可以完成4个信息的模糊匹配啦
那么显示全部呢?
即可完成清除搜索框进行全部查询
优化!
将数据之间关系添加到搜索下拉框下,代码进行修改:
后端DrawingManagementSystem\filter.py:
前端:
// 获取所有传感器信息
const getSensors = () => {
Api.sensors.getAll().then((res)=>{
Data.sensorOptions = res.data;
})
}
// 获取相应传感器下的项目信息
const getProjects = () => {
// 准备条件
let params = {
sensor:Data.sensorSelected,
}
Api.projects.getAll(params).then((res)=>{
Data.projectOptions = res.data;
})
}
// 获取相应项目下的资料信息
const getDatas = () => {
let params = {
sensor:Data.sensorSelected,
project:Data.projectOptions,
}
return Api.datas.getAll(params).then((res)=>{
Data.dataOptions = res.data;
})
}
<el-form :inline="true" class="demo-form-inline" style="display: flex">
<el-form-item label="查询条件:">
<el-input v-model="Data.q_str" placeholder="请输入查询条件" clearable/>
</el-form-item>
<el-form-item label="传感器:">
<el-select v-model="Data.sensorSelected" placeholder="请选择传感器" clearable filterable @change="getProjects">
<el-option
v-for="item in Data.sensorOptions"
:key="item.id"
:label="item.sensor_name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="项目:">
<el-select v-model="Data.projectSelected" placeholder="请选择项目" clearable filterable @change="getDatas">
<el-option
v-for="item in Data.projectOptions"
:key="item.id"
:label="item.project_name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="资料:">
<el-select v-model="Data.dataSelected" placeholder="请选择资料" clearable filterable>
<el-option
v-for="item in Data.dataOptions"
:key="item.id"
:label="item.data_name"
:value="item.id" />
</el-select>
</el-form-item>
效果为:
显示全部数据进行优化:
// 显示全部的数据
const listAllData = () =>{
Data.q_str = "";
Data.sensorSelected = "";
Data.projectSelected = "";
Data.dataSelected = "";
getData();
}
7、配置全局变量
有时候api是多个vue通用的一个个引入似乎有点麻烦
其实可以把api挂载到全局对象
vue2中:vue.prototype.$api=api
vue3中:app.config.globalProperties.api = api
好了,我们开整!
src\main.ts
src\views\datasys\DrawingMS.vue
拿下
8、axios请求优化(三)
一般请求需要设计拦截器
拦截器:
客户端发送http请求到前端web(触发请求拦截器)-> HTTP请求到后端接口 -> 后端发送ORM从数据库拿取数据 -> 返回数据(JSON)到前端web(触发响应拦截器)-> 数据渲染页面给用户
拦截器应用举例:
请求拦截器:自动添加身份验证token
我们所有的请求返回的错误都有http response code,这个可以封装到拦截器中,这样不用每处请求都写try catch
这边推荐下axios官网:有关拦截器介绍
拦截器 | Axios 中文文档 | Axios 中文网 (axios-http.cn)
对src\utils\request.ts进行优化
// 导入
import axios from "axios";
import { ElMessage } from "element-plus";
// 创建一个Axios的app
const request = axios.create({
baseURL:'http://localhost:8000/DrawingApi/v1/',
timeout:5000
})
// === 请求拦截器(request)======
request.interceptors.request.use(
(config: any) => {
// 获取本地loalstorage中的token
let token = localStorage.getItem('token')
// 判断是否有token
if (token) {
config.headers.common['token'] = token;
}
// 返回return
return config
},
(error: any) => {
Promise.reject(error);
}
)
// ==== 响应拦截器(response)=====
request.interceptors.response.use(
(response: any) => response,
(error: any) => {
if (error && error.response) {
error.data = {};
switch (error.response.status) {
case 400:
error.data.msg = '错误请求';
ElMessage.error(error.data.msg)
break
case 401:
error.data.msg = '未授权,请重新登录';
ElMessage.error(error.data.msg)
break
case 403:
error.data.msg = '拒绝访问';
ElMessage.error(error.data.msg)
break
case 404:
error.data.msg = '请求错误,未找到该资源';
ElMessage.error(error.data.msg)
break
case 405:
error.data.msg = '请求方法未允许';
ElMessage.error(error.data.msg)
break
case 408:
error.data.msg = '请求超时';
ElMessage.error(error.data.msg)
break
case 500:
error.data.msg = '服务器端出错';
ElMessage.error(error.data.msg)
break
case 501:
error.data.msg = '网络未实现';
ElMessage.error(error.data.msg)
break
case 502:
error.data.msg = '网络错误';
ElMessage.error(error.data.msg)
break
case 503:
error.data.msg = '服务不可用';
ElMessage.error(error.data.msg)
break
case 504:
error.data.msg = '网络超时';
ElMessage.error(error.data.msg)
break
case 505:
error.data.msg = 'http版本不支持该请求';
ElMessage.error(error.data.msg)
break
default:
error.data.msg = `连接错误${error.response.status}`;
ElMessage.error(error.data.msg)
}
} else {
error.data.msg = "连接到服务器失败";
ElMessage.error(error.data.msg)
}
return Promise.reject(error);
}
);
// 暴露
export default request
9、图纸信息弹出框布局
我们的图纸信息的添加、修改、查看都需要用弹窗进行展示
在src\views\datasys\DrawingMS.vue代码添加:
<!-- 4、弹出层 -->
<el-dialog v-model="Data.dialogFormVisible" width="40%">
<!-- 标题 -->
<template #title>
<div style="font-size:18px;color:#409eff;font-weight:bold;text-align:left;">{{Data.layerTitle}}</div>
</template>
<el-form v-model=Data.laberForm :inline="true" label-width="100px">
<el-form-item label="传感器:">
<el-select v-model="Data.laberForm.sensor" placeholder="请选择传感器" clearable filterable @change="getProjects">
<el-option
v-for="item in Data.sensorOptions"
:key="item.id"
:label="item.sensor_name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="项目:">
<el-select v-model="Data.laberForm.project" placeholder="请选择项目" clearable filterable @change="getDatas">
<el-option
v-for="item in Data.projectOptions"
:key="item.id"
:label="item.project_name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="资料:">
<el-select v-model="Data.laberForm.data" placeholder="请选择资料" clearable filterable>
<el-option
v-for="item in Data.dataOptions"
:key="item.id"
:label="item.data_name"
:value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="材料名称:">
<el-input v-model="Data.laberForm.drawing_name" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
<el-form-item label="物料编号:">
<el-input v-model="Data.laberForm.material_code" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
<el-form-item label="规格/图纸号:">
<el-input v-model="Data.laberForm.drawing_spec" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
<el-form-item label="图纸页数:">
<el-input v-model="Data.laberForm.drawing_page" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
<el-form-item label="客户编号:">
<el-input v-model="Data.laberForm.drawing_client_id" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
<el-form-item label="版本:">
<el-input v-model="Data.laberForm.drawing_version" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
<el-form-item label="备注:">
<el-input v-model="Data.laberForm.drawing_remark" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" >提交</el-button>
<el-button @click="closeLayer">取消</el-button>
</span>
</template>
</el-dialog>
效果为:
10、表单数据填充
对表单进行数字填充:
// 添加图纸信息
const adddrawing = () =>{
Data.dialogFormVisible = true;
Data.layerTitle = "【添加图纸】";
}
// 查看图纸信息
const viewdrawing = (row:any) =>{
Data.isView = true;
Data.dialogFormVisible = true;
Data.layerTitle = "【查看图纸信息】";
Data.drawingForm = JSON.parse(JSON.stringify(row));
Data.layerSPDSelected = row.data.id;
}
// 编辑图纸信息
const editdrawing = (row:any) =>{
Data.isEdit = true;
Data.dialogFormVisible = true;
Data.layerTitle = "【编辑图纸信息】";
Data.drawingForm = JSON.parse(JSON.stringify(row));
Data.layerSPDSelected = row.data.id;
}
// 关闭弹出层
const closeLayer = () =>{
Data.dialogFormVisible = false;
Data.isEdit = false;
Data.isView = false;
// 初始化表单
Data.drawingForm.sensor="";
Data.drawingForm.project="";
Data.drawingForm.data="";
Data.drawingForm.drawing_name="";
Data.drawingForm.material_code="";
Data.drawingForm.drawing_spec="";
Data.drawingForm.drawing_page="";
Data.drawingForm.drawing_client_id="";
Data.drawingForm.drawing_version="";
Data.drawingForm.drawing_remark="";
Data.layerSPDSelected="";
proxy.$refs.drawingForRef.resetFields();
}
// 弹出层树状结构
const getTreeSPD = async() =>{
// 定义
let allSensors = reactive([]);
let allProjects = reactive([]);
let allDatas = reactive([]);
// 获取所有的传感器
await Api.sensors.getAll().then((res)=>{
allSensors = res.data;
})
// 获取所有的项目
await Api.projects.getAll().then((res)=>{
allProjects = res.data;
})
// 获取所有的资料
await Api.datas.getAll().then((res)=>{
allDatas = res.data;
})
// 组合数据
for(let sensors of allSensors){
// 定义需要的结构
var obj = reactive({
value:`${sensors.id}-${sensors.sensor_name}`,
label:sensors.sensor_name,
children:[],
depth: 1
})
// 遍历项目填充obj的children
for(let project of allProjects){
if(project.sensor.id === sensors.id){
var obj1 = reactive({
value:project.id,
label:project.project_name,
children:[],
depth: 2
});
for(let data of allDatas){
if(data.project.id === project.id){
obj1.children.push({
value:data.id,
label:data.data_name,
depth: 3
});
}
}
obj.children.push(obj1)
}
}
Data.layerSPD.push(obj)
}
}
const nodeClick = (node: any) => {
if(node.depth === 3)
{
console.log(node)
}
};
<!-- 4、弹出层 -->
<el-dialog v-model="Data.dialogFormVisible" width="40%" @close="closeLayer">
<!-- 标题 -->
<template #title>
<div style="font-size:18px;color:#409eff;font-weight:bold;text-align:left;">{{Data.layerTitle}}</div>
</template>
<el-form ref="drawingForRef" :model=Data.drawingForm :inline="true" label-width="100px" :rules="Data.rules">
<el-form-item label="传感器/项目/资料">
<el-cascader
v-model="Data.layerSPDSelected"
placeholder="选择"
:options="Data.layerSPD"
filterable
style="width:555px"
:disabled="Data.isView"
/>
</el-form-item>
<el-form-item label="材料名称:" prop="drawing_name">
<el-input v-model="Data.drawingForm.drawing_name" :disabled="Data.isEdit || Data.isView" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
<el-form-item label="物料编号:" prop="material_code">
<el-input v-model="Data.drawingForm.material_code" :disabled="Data.isEdit || Data.isView" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
<el-form-item label="规格/图纸号:" prop="drawing_spec">
<el-input v-model="Data.drawingForm.drawing_spec" :disabled="Data.isEdit || Data.isView" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
<el-form-item label="图纸页数:" prop="drawing_page">
<el-input v-model="Data.drawingForm.drawing_page" :disabled="Data.isView" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
<el-form-item label="客户编号:" prop="drawing_client_id">
<el-input v-model="Data.drawingForm.drawing_client_id" :disabled="Data.isView" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
<el-form-item label="版本:" prop="drawing_version">
<el-input v-model="Data.drawingForm.drawing_version" :disabled="Data.isView" :suffix-icon="Edit" placeholder="请输入" />
</el-form-item>
<el-form-item label="备注:" prop="drawing_remark">
<el-input v-model="Data.drawingForm.drawing_remark" :disabled="Data.isView" :suffix-icon="Edit" placeholder="请输入" style="width:555px"/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button type="primary" v-show="!Data.isView" @click="commitdrawing">提交</el-button>
<el-button @click="closeLayer">取消</el-button>
</span>
</template>
</el-dialog>
11、表单的校验
12、完成图纸的添加
前端代码:
// 弹出层表单的提交
const commitdrawing = () =>{
proxy.$refs.drawingForRef.validate((valid: boolean)=>{
if (valid){
if(Data.layerSPDSelected[2]){
Data.drawingForm.data = Data.layerSPDSelected[2];
Data.drawingForm.project = Data.layerSPDSelected[1];
Data.drawingForm.sensor = parseInt(Data.layerSPDSelected[0].split('-')[0],10);
}else{
Data.drawingForm.data = Data.layerSPDSelected
}
// 修改or提交
if(Data.isEdit){
// 修改
Api.drawings.edit(Data.drawingForm).then((res)=>{
if(res.status === 200){
// 重新加载数据
getData();
closeLayer();
ElMessage({
message:"图纸修改成功!",
type:'success'
})
}
})
}else{
// 添加选择
Data.drawingForm.data = Data.layerSPDSelected[2];
Data.drawingForm.project = Data.layerSPDSelected[1];
Data.drawingForm.sensor = parseInt(Data.layerSPDSelected[0].split('-')[0],10);
// 添加
Api.drawings.add(Data.drawingForm).then((res)=>{
// 重新加载数据
getData();
closeLayer();
ElMessage({
message:"图纸添加成功!",
type:'success'
})
})
}
}
})
};
后端代码:
class DrawingViewSet(ModelViewSet):
queryset = Drawing.objects.all()
serializer_class = DrawingSerialzer
pagination_class = MyPageNumberPagination
# 指定筛选的类
filter_class = DrawingFilter
# 指定查找匹配的字段
search_fields = ('drawing_name', 'material_code', 'drawing_spec', 'drawing_client_id')
# 添加
def create(self, request, *args, **kwargs):
# 接受传递值
rec = request.data
try:
Drawing.objects.create(
drawing_name=rec.get('drawing_name'), drawing_spec=rec.get('drawing_spec'),
material_code=rec.get('material_code'),
drawing_client_id=rec.get('drawing_client_id'),
drawing_page=rec.get('drawing_page'),
drawing_version=rec.get('drawing_version'),
drawing_remark=rec.get('drawing_remark'),
data_id=rec.get('data'),
sensor_id = rec.get('sensor'),
project_id = rec.get('project')
)
return Response({'msg': '添加信息成功'}, status=status.HTTP_201_CREATED)
except Exception as e:
print(e)
return Response({'error': '添加信息失败'}, status=status.HTTP_400_BAD_REQUEST)
效果为:
13、完成图纸的修改
前端:
// 弹出层表单的提交
const commitdrawing = () =>{
proxy.$refs.drawingForRef.validate((valid: boolean)=>{
if (valid){
if(Data.layerSPDSelected[2]){
Data.drawingForm.data = Data.layerSPDSelected[2];
Data.drawingForm.project = Data.layerSPDSelected[1];
Data.drawingForm.sensor = parseInt(Data.layerSPDSelected[0].split('-')[0],10);
}else{
Data.drawingForm.data = Data.layerSPDSelected
}
// 修改or提交
if(Data.isEdit){
// 修改
console.log(Data.drawingForm)
Api.drawings.edit(Data.drawingForm.id,Data.drawingForm).then((res)=>{
if(res.status === 201){
// 重新加载数据
getData();
closeLayer();
ElMessage({
message:"图纸修改成功!",
type:'success'
})
}
})
}else{
// 添加选择
Data.drawingForm.data = Data.layerSPDSelected[2];
Data.drawingForm.project = Data.layerSPDSelected[1];
Data.drawingForm.sensor = parseInt(Data.layerSPDSelected[0].split('-')[0],10);
// 添加
Api.drawings.add(Data.drawingForm).then((res)=>{
// 重新加载数据
getData();
closeLayer();
ElMessage({
message:"图纸添加成功!",
type:'success'
})
})
}
}
})
};
后端:
def update(self, request, *args, **kwargs):
# 接受传递值
rec = request.data
try:
drawing = Drawing.objects.get(pk=kwargs.get('pk'))
# drawing.drawing_name = rec.get('drawing_name')
# drawing.drawing_spec = rec.get('drawing_spec')
# drawing.material_code = rec.get('material_code')
drawing.drawing_client_id = rec.get('drawing_client_id')
drawing.drawing_page = rec.get('drawing_page')
drawing.drawing_version = rec.get('drawing_version')
drawing.drawing_remark = rec.get('drawing_remark')
drawing.data_id = rec.get('data')
drawing.sensor_id = rec.get('sensor')
drawing.project_id = rec.get('project')
drawing.save()
return Response({'msg': '修改信息成功'}, status=status.HTTP_201_CREATED)
except Exception as e:
print(e)
return Response({'error': '修改信息失败'}, status=status.HTTP_400_BAD_REQUEST)
这样就可以完成指定的修改了
14、完成图纸的删除
// 实现图纸的删除
const deldrawing=(row:any)=>{
let confirmStr = "您确定要删除图纸信息吗?包含文件!</br>材料名称:" +
row.drawing_name +
"</br>物料编号:" + row.material_code;
ElMessageBox.confirm(confirmStr,'警告',{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
dangerouslyUseHTMLString: true,
}).then(()=>{
// 删除!
Api.drawings.del(row.id).then((res)=>{
if(res.status === 204){
// 重新加载数据
getData();
closeLayer();
ElMessage({
message:"图纸删除成功!",
type:'success'
})
}
})
})
}
现在就可以完成删除了!
15、完成图纸的上传和下载
15.1、 上传文件的基本介绍
如何存储文件
1、把文件系列化成文本,把文本存储在数据库
针对较小的文件
2、把文件存储在后端文件夹中,把路径存储在数据库中
比较通用
过程分析:
1、选择文件上传到后端
2、生成唯一名字存在后端服务器文件夹
3、名字存储在DB
4、返回文件名给前端
5、根据返回的名字,进行下载
如何保证上传文件不重名
方案01:以当前的日期时间+随机数命名
方案02:使用python中uuid模块生成不相同的名字
15.2、 完成文件上传的后台接口
大致步骤:
1、配置存储文件夹
2、Settings中设定media
3、设置url
4、完成接口函数代码
在后端添加下列文件
'''
@Project :gveInformationSystemBE
@File :myupload.py
@IDE :PyCharm
@Author :HXW
@Date :2023/8/2 13:11
本处实现文件的上传:图片,视频等
上传任务通过upload_file实现,upload_file中有三个参数,分别为:
1:file——提交的文件
2:path——存储的子目录
3;type——文件命名的类型
(1)——时间日期+随机值
(2)——uuid
返回值描述:
成功:status:True,Data:新写入的文件名
失败:status:False,error:错误描述
'''
from datetime import datetime
import random
import uuid
from django.conf import settings
import os
def get_file_name_random_date():
"""根据日期获取随机值"""
filename = datetime.now().strftime("%Y%m%d%H%M%S").replace("-", "")
filename += str(random.randint(1000, 9999))
return filename
def update_file(file, path: str, type: int):
"""
提供文件的上传
:param file:要上传的文件
:param path:提供的路径
:param type:随机命名的方式 1-- 时间日期随机值 2-- uuid
:return:
"""
# 定义一个new_name获取新路径
new_name = ""
if type == 1:
new_name = get_file_name_random_date()
elif type == 2:
new_name = uuid.uuid4().hex
# 拼接路径
file_name = settings.MEDIA_ROOT + os.path.sep + path + os.path.sep + new_name + os.path.splitext(file.name)[1]
# 开始写入
try:
f = open(file_name, 'wb')
# 分多次写入
for i in file.chunks():
f.write(i)
f.close()
return {'status': True, 'data': new_name + os.path.splitext(file.name)[1]}
except Exception as e:
return {'status': False, 'error': '文件写入磁盘出现异常'}
对views.py中DrawingViewSet新增:
# 上传
@action(methods=['post','get'],detail=False)
def upload(self, request, *args, **kwargs):
# 接受前端传递的文件
rev_file = request.FILES.get('file')
if not rev_file:
return Response(status=status.HTTP_400_BAD_REQUEST)
res = myupload.update_file(rev_file,'file',1)
return Response(res)
头文件导入:
效果为:http://localhost:8000/DrawingApi/v1/Drawings/upload/
15.3、 对前端api管理区域进行添加
还记得我们之前有一个继承的api吗?
import request from "../utils/request";
import APIBase from "./apibase";
// 继承
export default class apiplusApi extends APIBase{
// 构造器
constructor(name:string){
super(name)
}
public upload = (data:any)=>{
return request({
method:'post',
url:`${this.name}/upload/`,
data
})
}
}
对src\api\index.ts进行修改:
// 导入基础的类
import APIBase from "./apibase";
import apiplusApi from "./apiplus"
// 实例化对象
let sensors = new APIBase("Sensors")
let projects = new APIBase("Projects")
let datas = new APIBase("Datas")
let drawings = new apiplusApi("Drawings")
// 封装暴露
export default{
sensors,projects,datas,drawings,
}
这样我们的upload接口就挂载到drawings下了
// 上传文件
const uploadDrawing = (file:any) =>{
// 文件在axios中一般封装在formdata类中
// 1、定义formData类
let fileReq = new FormData();
fileReq.append('file',file.file);
// 请求
Api.drawings.upload(fileReq).then((res)=>{
Data.drawingForm.drawing_url = '/media/file/' + res.data.data;
})
}
在弹出框中加入上传按钮
效果为:
当勾选后,就会自动上传了。
我们对后端再优化下:
这个时候我们发现,不仅apps\media\file下已经有文件了,同时数据库也存储了地址:
尾声:
目前我们已经完成图纸管理系统的大部分功能了,基本上展示一个基础系统全栈的方方面面。
还需优化:
1、加入下载功能。
2、图纸的传感器类、项目类、资料类需要以网页操作形式添加。
3、权限管理即普通用户只有浏览和下载权限,管理员为全部操作。
4、树的联动(只展示了树没有更新)
5、其他友好型优化等
后期我会将下载功能添加后,开源到仓库,供大家学习研究。最后感谢大家的赏脸和赞同,我任然有许多不足需要继续学习。大家加油!