vue

dvadmin快速crud教程

发布于 2022-04-09 09:23:19

1. 后端

创建django app

python manage.py startapp crud_demo

image.png

在setting.py里添加我们的app

image.png

编写models

from django.db import models

# Create your models here.
from dvadmin.utils.models import CoreModel


class CrudDemoModel(CoreModel):
    goods = models.CharField(max_length=255, verbose_name="商品")
    inventory = models.IntegerField(verbose_name="库存量")
    goods_price = models.FloatField(verbose_name="商品定价")
    purchase_goods_date = models.DateField(verbose_name="进货时间")

    class Meta:
        db_table = "goods"
        verbose_name = '商品表'
        verbose_name_plural = verbose_name
        ordering = ('-create_datetime',)

新建serializers.py,编写我们的序列化器

'''
  @author: hongzai
  @contact: 2505811377@qq.com
  @file: serializers.py
  @time: 2022/4/8 11:12
  @desc:
  '''
from crud_demo.models import CrudDemoModel
from dvadmin.utils.serializers import CustomModelSerializer


class CrudDemoModelSerializer(CustomModelSerializer):
    """
    序列化器
    """

    class Meta:
        model = CrudDemoModel
        fields = "__all__"


class CrudDemoModelCreateUpdateSerializer(CustomModelSerializer):
    """
    创建/更新时的列化器
    """

    class Meta:
        model = CrudDemoModel
        fields = '__all__'

编写视图views

# Create your views here.
from crud_demo.models import CrudDemoModel
from crud_demo.serializers import CrudDemoModelSerializer, CrudDemoModelCreateUpdateSerializer
from dvadmin.utils.viewset import CustomModelViewSet


class CrudDemoModelViewSet(CustomModelViewSet):
    """
    list:查询
    create:新增
    update:修改
    retrieve:单例
    destroy:删除
    """
    queryset = CrudDemoModel.objects.all()
    serializer_class = CrudDemoModelSerializer
    create_serializer_class = CrudDemoModelCreateUpdateSerializer
    update_serializer_class = CrudDemoModelCreateUpdateSerializer
    filter_fields = ['goods', 'goods_price']
    search_fields = ['goods']

新建urls.py并添加路由

'''
  @author: hongzai
  @contact: 2505811377@qq.com
  @file: urls.py
  @time: 2022/4/8 12:23
  @desc:
  '''
from rest_framework.routers import SimpleRouter

from .views import CrudDemoModelViewSet

router = SimpleRouter()
router.register("api/crud_demo", CrudDemoModelViewSet)

urlpatterns = [
]
urlpatterns += router.urls

在application的urls里导入我们的app

path('',include('crud_demo.urls'))

image.png

迁徙我们的app

python manage.py makemigrations
python manage.py migrate

image.png

运行项目并查看接口

打开swgger可以看到我们配置的接口已经生效了

image.png

2. 前端

编写前端curd页面

dvadmin的前端是基于d2admin的管理系统,集成了d2-crud-plus等d2admin生态圈的功能
d2-crud-plus 是基于 d2-admin 的 d2-crud 的扩展,旨在简化 d2-crud 配置,快速开发crud功能

d2-crud-plus

打开web/src/views/新建一个目录为crud_demo,并新建crud.js,index.vue,api.js三个文件

目录结构

    views
     |--crud_demo
      |--api.js  //定义api接口
      |--crud.js //配置crud界面
      |--index.vue //vue文件
      
        

api.js

/*
 * @Description: 
 * @Author: hongzai
 * @version: 
 * @Date: 2022-04-08 12:44:55
 * @LastEditors: hongzai
 * @LastEditTime: 2022-04-08 12:44:55
 */

import { request } from '@/api/service'
export const urlPrefix = '/api/crud_demo/'

export function GetList(query) {
  return request({
    url: urlPrefix,
    method: 'get',
    params: query
  })
}

export function AddObj(obj) {
  return request({
    url: urlPrefix,
    method: 'post',
    data: obj
  })
}

export function UpdateObj(obj) {
  return request({
    url: urlPrefix + obj.id + '/',
    method: 'put',
    data: obj
  })
}

export function DelObj(id) {
  return request({
    url: urlPrefix + id + '/',
    method: 'delete',
    data: { id }
  })
}

crud.js

import { request } from "@/api/service";
import { BUTTON_STATUS_NUMBER } from "@/config/button";
import { urlPrefix as bookPrefix } from "./api";


export const crudOptions = vm => {
    return {
        pageOptions: {
            compact: true
        },
        options: {
            tableType: "vxe-table",
            rowKey: true, // 必须设置,true or false
            rowId: "id",
            height: "100%", // 表格高度100%, 使用toolbar必须设置
            highlightCurrentRow: false
        },
        rowHandle: {
            width: 140,
            view: {
                thin: true,
                text: "",
                disabled() {
                    return !vm.hasPermissions("Retrieve");
                }
            },
            edit: {
                thin: true,
                text: "",
                disabled() {
                    return !vm.hasPermissions("Update");
                }
            },
            remove: {
                thin: true,
                text: "",
                disabled() {
                    return !vm.hasPermissions("Delete");
                }
            }
        },
        indexRow: {
            // 或者直接传true,不显示title,不居中
            title: "序号",
            align: "center",
            width: 100
        },
        viewOptions: {
            componentType: "form"
        },
        formOptions: {
            defaultSpan: 24, // 默认的表单 span
            width: "35%"
        },
        columns: [{
                title: "ID",
                key: "id",
                show: false,
                disabled: true,
                width: 90,
                form: {
                    disabled: true
                }
            },
            {
                title: "商品",
                key: "goods",
                sortable: true,
                treeNode: true,

                type: "input",
                form: {
                    editDisabled: true,
                    rules: [
                        // 表单校验规则
                        { required: true, message: "商品名称必填" }
                    ],
                    component: {
                        props: {
                            clearable: true
                        },
                        placeholder: "请输入商品"
                    },
                    itemProps: {
                        class: { yxtInput: true }
                    }
                }
            },
            {
                title: "库存量",
                key: "inventory",
                sortable: true,
                type: "number",
                form: {
                    editDisabled: true,
                    rules: [
                        // 表单校验规则
                        { required: true, message: "库存量必填" }
                    ],
                    component: {
                        props: {
                            clearable: true
                        },
                        placeholder: "请输入库存量"
                    },
                    itemProps: {
                        class: { yxtInput: true }
                    }
                }
            },
            {
                title: "商品定价",
                key: "goods_price",
                sortable: true,

                search: {
                    component: {
                        props: {
                            clearable: true
                        }
                    }
                },

                type: "number",
                form: {
                    editDisabled: true,
                    rules: [
                        // 表单校验规则
                        { required: true, message: "商品定价必填" }
                    ],
                    component: {
                        props: {
                            clearable: true
                        },
                        placeholder: "请输入商品定价"
                    },
                    itemProps: {
                        class: { yxtInput: true }
                    }
                }
            },
            {
                title: "进货时间",
                key: "purchase_goods_date",
                sortable: true,
                type: "date",
                form: {
                    rules: [
                        { required: true, message: "进货时间必填" }
                    ],
                    component: {
                        props: {
                            clearable: true,
                            format: 'yyyy-MM-dd',
                            valueFormat: 'yyyy-MM-dd'
                        },
                        placeholder: "请输入进货时间"
                    },
                    itemProps: {
                        class: { yxtInput: true }
                    }
                }
            }
        ].concat(vm.commonEndColumns())
    };
};

index.vue

<!--
 * @Description:
 * @Author: hongzai
 * @version:
 * @Date: 2022-04-08 12:44:41
 * @LastEditors: hongzai
 * @LastEditTime: 2022-04-08 12:54:31
-->
<template>
  <d2-container :class="{ 'page-compact': crud.pageOptions.compact }">
    <template slot="header">测试页面</template>
    <d2-crud-x ref="d2Crud" v-bind="_crudProps" v-on="_crudListeners">
      <!-- 自动绑定参数与事件 -->
      <!-- 包含详细参数见:https://gitee.com/greper/d2-crud-plus/blob/master/packages/d2-crud-plus/src/lib/mixins/crud.js#L164-->
      <div slot="header">
        <crud-search
          ref="search"
          :options="crud.searchOptions"
          @submit="handleSearch"
        />
        <el-button-group>
          <el-button size="small" type="primary" @click="addRow"
            ><i class="el-icon-plus" /> 新增</el-button
          >
        </el-button-group>
        <crud-toolbar v-bind="_crudToolbarProps" v-on="_crudToolbarListeners" />
      </div>
    </d2-crud-x>
  </d2-container>
</template>

<script>
import { crudOptions } from './crud' // 上文的crudOptions配置
import { d2CrudPlus } from 'd2-crud-plus'
import { AddObj, GetList, UpdateObj, DelObj } from './api' // 查询添加修改删除的http请求接口
export default {
  name: 'curd_demo',
  mixins: [d2CrudPlus.crud], // 最核心部分,继承d2CrudPlus.crud
  methods: {
    getCrudOptions () {
      return crudOptions(this)
    },
    pageRequest (query) {
      return GetList(query)
    }, // 数据请求
    addRequest (row) {
      return AddObj(row)
    }, // 添加请求
    updateRequest (row) {
      return UpdateObj(row)
    }, // 修改请求
    delRequest (row) {
      return DelObj(row.id)
    } // 删除请求
  }
}
</script>

在前端添加菜单

image.png

image.png

刷新前端页面就可以看到我们的菜单

image.png

添加测试数据

image.png

d2-crud-plus文档
默认crud的配置可以参考文档根据你的需求

image.png

ok,现在可以看到我们的数据了

image.png

image.png

数据库里也看到了我们的数据

如何使用系统自带的文件管理接口上传文件

image.png

dvadmin/system/views/file_list.py里能够找到系统自带的文件管理的modelserializers和modelviewset

image.png
在urls里找到接口的路由

打开前端web目录,根目录的install.js这里有全局crud的配置

image.png

这里就是d2uploader文件上传控件的配置,因为我们使用的是本地服务器存储,而不是第三方储存,我们只修改form的配置

form: {
    action: util.baseURL() + 'api/system/file/',
    name: 'file',
    headers: {
      Authorization: 'JWT ' + util.cookies.get('token')
    },
    type: 'form',
    successHandle(ret, option) {
      if (ret.data == null || ret.data === '') {
        throw new Error('上传失败')
      }
      return { url: ret.data.url }
    }
  }

crud里类型填写file-uploader,在对应的事件处理数据

image.png

{
        title: '选择数据',
        key: 'algorithmData',
        valueResolve(row, key) {
          if (row.algorithmData.length > 0) {
            row.algorithmData = row.algorithmData[0]
          }

        },
        sortable: true,
        search: {
          disabled: true
        },
        type: 'file-uploader',

        form: {
          props: { //表单组件的参数,具体参数请查看对应的组件文档
            drag: true
          },

          valueChange(key, value, form, { getColumn, mode, component, immediate, getComponent }) {
            // form表单数据change事件,表单值有改动将触发此事件
            console.log(form[key]);
          },
        }
      },

image.png
上传文件测试,ok,看一下文件管理

image.png
可以看到我们的文件上传成功

在d2admin里使用echarts

使用方法和官网的demo相同

1.在index里把d2-crud-x表格注释,或者你使用插槽把展示图表的div放进去也可以,我这里使用第一种方式。
2.在api.js里添加我们获取数据的接口

echarts demo

index.vue

<!--
 * @Author: your name
 * @Date: 2022-04-14 14:13:20
 * @LastEditTime: 2022-04-17 16:15:40
 * @LastEditors: hongzai
 * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 * @FilePath: \web\src\views\data_visual\index.vue
-->

<template>
  <d2-container :class="{ 'page-compact': crud.pageOptions.compact }">
    <div class="background">
      <template slot="header">测试页面1</template>
      <!-- <d2-crud-x ref="d2Crud" v-bind="_crudProps" v-on="_crudListeners">
        <div slot="header">
          <crud-search
            ref="search"
            :options="crud.searchOptions"
            @submit="handleSearch"
          />
          <el-button-group>
            <el-button size="small" type="primary" @click="addRow"
              ><i class="el-icon-plus" /> 新增</el-button
            >
          </el-button-group>
          <crud-toolbar
            :search.sync="crud.searchOptions.show"
            :compact.sync="crud.pageOptions.compact"
            :columns="crud.columns"
            @refresh="doRefresh()"
            @columns-filter-changed="handleColumnsFilterChanged"
          />
        </div>
      </d2-crud-x> -->

      <dir>
        <el-form ref="form" :model="form" label-width="200px">
          <el-form-item label="选择已导入的算法和数据">
            <el-select
              v-model="value"
              placeholder="请选择"
              style="width: 500px"
              @change="change"
            >
              <el-option
                v-for="item in dataList"
                :key="item.id"
                :label="item.label"
                :value="item"
              >
                <span style="float: left">{{ item.label }}</span>
                <span style="float: right; color: #8492a6; font-size: 13px">{{
                  item.value
                }}</span>
              </el-option>
            </el-select>
          </el-form-item>

          <el-form-item>
            <el-button type="primary" @click="onSubmit">展示</el-button>
          </el-form-item>
        </el-form>
      </dir>
      <div id="chart"></div>
    </div>
  </d2-container>
</template>

<script>
import * as api from "./api";
import { crudOptions } from "./crud";
import { d2CrudPlus } from "d2-crud-plus";
import * as echarts from "echarts";
import { process_chart_data1 } from "./process_data.js";
export default {
  name: "data_visual",
  mixins: [d2CrudPlus.crud],
  mounted() {
    this.pageRequest().then((res) => {
      let resList = res.data.data;
      resList.forEach((val, index) => {
        this.dataList.push({
          value: val.algorithmData,
          label: val.algorithmName,
        });
      });
      console.log(this.dataList);
    });
  },
  data() {
    return {
      form: {},
      dataList: [],
      value: "",
    };
  },
  methods: {
    change(val) {
      console.log(val);
    },
    onSubmit() {
      // 在这设置图表option
      api
        .getFileData({
          filePath: this.value.value,
        })
        .then((res) => {
          console.log(res.data.data);
          this.data = res.data.data;

          switch (this.value.label) {
            case "逻辑回归":
              echarts.init(document.getElementById("chart")).setOption({
                xAxis: {},
                yAxis: {},
                series: [
                  {
                    symbolSize: 20,
                    data: this.data,
                    type: "scatter",
                  },
                ],
              });
              break;
            case "线性回归":
              echarts.init(document.getElementById("chart")).setOption({
                xAxis: {},
                yAxis: {},
                series: [
                  {
                    symbolSize: 20,
                    data: this.data,
                    type: "scatter",
                  },
                ],
              });
              break;

            default:
              break;
          }
        });
    },
    getCrudOptions() {
      return crudOptions(this);
    },
    pageRequest(query) {
      return api.GetList(query);
    },
    addRequest(row) {
      console.log("api", api);
      return api.AddObj(row);
    },
    updateRequest(row) {
      console.log("----", row);
      return api.UpdateObj(row);
    },
    delRequest(row) {
      return api.DelObj(row.id);
    },
  },
};
</script>
<style>
#chart {
  width: 800px;
  height: 500px;
}
.background {
  height: 100vh;
  /* background-image: url("index_bg.png"); */
}
</style>

api.js

export function getFileData(obj) {
  return request({
    url: '/api/get_file_data',
    method: 'post',
    data: obj
  })
}

效果如图

image.png

如何使用时间字段过滤后端数据

前端

{
        title: "日期范围",
        key: "daterange",
        sortable: true,
        type: "daterange",
        show: false,
        search: { disabled: false, width: 380 },
        form: {
          disabled: true,
          component: {
            props: {
              "time-arrow-control": false,
              "picker-options": { shortcuts: shortcuts },
              format: "yyyy-MM-dd",
              valueFormat: "yyyy-MM-dd",
            },
          },
        },
        valueBuilder(row, key) {
          if (!StringUtils.hasEmpty(row.daterangeStart, row.daterangeEnd)) {
            row.daterange = [
              new Date(row.daterangeStart),
              new Date(row.daterangeEnd),
            ];
          }
        },
        valueResolve(row, key) {
          if (row.daterange != null && row.daterange.length > 1) {
            row.start_time = row.daterange[0];
            row.stop_time = row.daterange[1];
          } else {
            row.start_time = null;
            row.stop_time = null;
          }
        },
      },
      

后端

class NucleicAcidInfoModelFilter(django_filters.rest_framework.FilterSet):
    # 指定区间过滤
    start_time = django_filters.DateFilter(field_name="create_datetime", lookup_expr='gte')
    stop_time = django_filters.DateFilter(field_name="create_datetime", lookup_expr='lte')
    

    class Meta:
        model = NucleicAcidInfoModel
        fields = ['identification_id']

image.png

在视图里添加filterset_class即可

41 条评论

发布
问题