一步步完整搭建一个图纸管理系统(Django+Vue3)——3、前后端交互

4,324 阅读9分钟

前言: 网上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

image.png

如何直接运行,会发现拿不到数据,因为我们没有对跨域进行处理

image.png

先在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

安装好后配置:

image.png

最后在settings.py中加入

# ============跨域请求=============
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_WHITELIST = (
    'http://localhost:8080',
)

运行后会发现可以拿到后端数据了

image.png

进行代码优化,把获取的数据放到前端页面上

image.png

效果为:

image.png

为了友好型,如果加载车成功给一个提示语句

导入

// 导入ElementPlus
import { ElMessage,ElMessageBox } from 'element-plus'

image.png

1.2 优化信息展示

对现实数据条数优化

image.png

我们发现拿到的数据现实属于什么传感器,属于什么项目都是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__"

效果为:

image.png

表格过长呢?

只需要在后面加: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文件如下:

image.png

新建utils文化如下:

image.png

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,
}

引用可为:

image.png

效果还是一样的:

image.png

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>

效果为:

image.png

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>

效果为:

image.png

思考:这里如果通过树进行搜索的话,那我搜索的下拉框其实作用就不大了,且没有层级绑定。

image.png

这边暂时不进行修改,为了更好的展示项目知识点,方便今后其他项目需要进行修改。后期系统优化时候,再进行修改。

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,
}

这样就可以把旧的四个都删除了

image.png

有人问 那我要是添加新的api呢,那可以用继承啊

image.png

6、查询和显示全部

查询的流程为:

文本框接受input-》模糊匹配-》字段:id啥的

-》search【匹配多个字段】

select[传感器]-》匹配-》传感信息

select[项目]-》匹配-》项目信息

select[资料]-》匹配-》资料信息

注意:三个是并且的关系

已知我们当初设置的多查询其实是多个字段:

image.png

只需要在getData函数中加入一个字段就行

image.png

并给按钮赋值一个点击函数

image.png

就可以完成4个信息的模糊匹配啦

那么显示全部呢?

image.png

image.png

即可完成清除搜索框进行全部查询

优化!

将数据之间关系添加到搜索下拉框下,代码进行修改:

后端DrawingManagementSystem\filter.py:

image.png 前端:

// 获取所有传感器信息
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>
​

效果为:

image.png

显示全部数据进行优化:

// 显示全部的数据
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

image.png

src\views\datasys\DrawingMS.vue

image.png

拿下

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代码添加:

image.png

image.png

<!-- 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>

效果为:

image.png

10、表单数据填充

对表单进行数字填充:

image.png

// 添加图纸信息
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、表单的校验

image.png

image.png

image.png

image.png

image.png

image.png

image.png

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)

效果为:

image.png

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)

这样就可以完成指定的修改了

image.png

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'
            })
            }
      })
  })
}

image.png

现在就可以完成删除了!

15、完成图纸的上传和下载

15.1、 上传文件的基本介绍

如何存储文件

1、把文件系列化成文本,把文本存储在数据库

针对较小的文件

2、把文件存储在后端文件夹中,把路径存储在数据库中

比较通用

过程分析:

1、选择文件上传到后端

2、生成唯一名字存在后端服务器文件夹

3、名字存储在DB

4、返回文件名给前端

5、根据返回的名字,进行下载

如何保证上传文件不重名

方案01:以当前的日期时间+随机数命名

方案02:使用python中uuid模块生成不相同的名字

15.2、 完成文件上传的后台接口

大致步骤:

1、配置存储文件夹

2、Settings中设定media

3、设置url

4、完成接口函数代码

在后端添加下列文件

image.png

'''
@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)
        

头文件导入:

image.png

效果为:http://localhost:8000/DrawingApi/v1/Drawings/upload/

image.png

15.3、 对前端api管理区域进行添加

还记得我们之前有一个继承的api吗?

image.png

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;
  })
}

在弹出框中加入上传按钮

image.png

效果为:

image.png

当勾选后,就会自动上传了。

我们对后端再优化下:

image.png

这个时候我们发现,不仅apps\media\file下已经有文件了,同时数据库也存储了地址:

image.png

尾声:

目前我们已经完成图纸管理系统的大部分功能了,基本上展示一个基础系统全栈的方方面面。

还需优化:

1、加入下载功能。

2、图纸的传感器类、项目类、资料类需要以网页操作形式添加。

3、权限管理即普通用户只有浏览和下载权限,管理员为全部操作。

4、树的联动(只展示了树没有更新)

5、其他友好型优化等

后期我会将下载功能添加后,开源到仓库,供大家学习研究。最后感谢大家的赏脸和赞同,我任然有许多不足需要继续学习。大家加油!