Skip to content

开发后端

创建应用

进入目录django-vue3-admin\backend,创建Django应用

python
python manage.py startapp my_demo # my_demo为应用名称

配置settings.py

进入目录django-vue3-admin\backend\application,修改文件settings.py,添加刚才创建的应用到Django项目中。

python
INSTALLED_APPS = [
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "django_comment_migrate",
    "rest_framework",
    "django_filters",
    "corsheaders",  
    "drf_yasg",
    "captcha",
    "channels",
    "dvadmin.system",
    "my_demo", # 新创建的应用示例
]

编写模型层

进入目录django-vue3-admin\backend\my_demo,修改文件models.py

python
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",)

编写序列化器

进入目录django-vue3-admin\backend\my_demo,新建文件serializers.py

python
from my_demo.models import CrudDemoModel
from dvadmin.utils.serializers import CustomModelSerializer


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

    class Meta:
        model = CrudDemoModel
        fields = "__all__"


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

    class Meta:
        model = CrudDemoModel
        fields = "__all__"

编写视图层

进入目录django-vue3-admin\backend\my_demo,修改文件views.py

python
from django.shortcuts import render

# Create your views here.
from my_demo.models import CrudDemoModel
from my_demo.serializers import (
    CurdDemoModelSerializer,
    CrudDemoModelCreateUpdateSerializer,
)
from dvadmin.utils.viewset import CustomModelViewSet


class CrudDemoModelViewSet(CustomModelViewSet):
    queryset = CrudDemoModel.objects.all()
    serializer_class = CurdDemoModelSerializer
    create_serializer_class = CrudDemoModelCreateUpdateSerializer
    update_serializer_class = CrudDemoModelCreateUpdateSerializer

编写应用路由:进入目录django-vue3-admin\backend\my_demo,新建文件urls.py

python
from rest_framework.routers import SimpleRouter
from .views import CrudDemoModelViewSet

router = SimpleRouter()
router.register("api/CrudDemoModelViewSet", CrudDemoModelViewSet)
print(type(router.urls))
urlpatterns = router.urls

将应用路由注册到项目路由:进入目录django-vue3-admin\backend\\application,修改文件urls.py

python
# 添加应用路由
my_urls = [
    path("", include("my_demo.urls")),
]
urlpatterns += my_urls

执行模型层迁移

进入目录django-vue3-admin\backend,执行模型层迁移

python
python manage.py makemigrations
python manage.py migrate

如果迁移成功,会在数据库中看到新建的数据表goods

dvadmin0201

开发前端

前端目录结构

进入目录django-vue3-admin\web\src\views,新建文件夹MyDemo,然后新建子文件夹CrudDemoModelViewSet。在文件夹CrudDemoModelViewSet下新建下面3个文件。

python
django-vue3-admin\web\src\views
                             |--MyDemo
                                |--CrudDemoModelViewSet
                                  |--api.ts  //定义api接口
                                  |--crud.tsx //配置crud界面
                                  |--index.vue //vue文件

编写api.ts文件

实现增、删、改、查(单个)、查(全部)和导出功能的API接口。按实际需求修改:

  • 修改URL前缀
  • 增删API接口
typescript
import { request, downloadFile } from '/@/utils/service';
import { PageQuery, AddReq, DelReq, EditReq, InfoReq } from '@fast-crud/fast-crud';
//与后端对接的URL前缀
export const apiPrefix = '/api/CrudDemoModelViewSet/';

//查(全部)
export function GetList(query: PageQuery) {
	return request({
		url: apiPrefix,
		method: 'get',
		params: query,
	});
}
//查(单个)
export function GetObj(id: InfoReq) {
	return request({
		url: apiPrefix + id,
		method: 'get',
	});
}
//增
export function AddObj(obj: AddReq) {
	return request({
		url: apiPrefix,
		method: 'post',
		data: obj,
	});
}
//改
export function UpdateObj(obj: EditReq) {
	return request({
		url: apiPrefix + obj.id + '/',
		method: 'put',
		data: obj,
	});
}
//删
export function DelObj(id: DelReq) {
	return request({
		url: apiPrefix + id + '/',
		method: 'delete',
		data: { id },
	});
}
//导出
export function exportData(params: any) {
	return downloadFile({
		url: apiPrefix + 'export_data/',
		params: params,
		method: 'get',
	});
}

编写crud.tsx文件

实现CRUD功能配置

typescript
import { CrudOptions, AddReq, DelReq, EditReq, dict, CrudExpose, UserPageQuery, CreateCrudOptionsRet } from '@fast-crud/fast-crud';
import _ from 'lodash-es';
import * as api from './api';
import { request } from '/@/utils/service';
import { auth } from '/@/utils/authFunction';

//此处为crudOptions配置
export default function ({ crudExpose }: { crudExpose: CrudExpose }): CreateCrudOptionsRet {
	const pageRequest = async (query: any) => {
		return await api.GetList(query);
	};
	const editRequest = async ({ form, row }: EditReq) => {
		if (row.id) {
			form.id = row.id;
		}
		return await api.UpdateObj(form);
	};
	const delRequest = async ({ row }: DelReq) => {
		return await api.DelObj(row.id);
	};
	const addRequest = async ({ form }: AddReq) => {
		return await api.AddObj(form);
	};

	const exportRequest = async (query: UserPageQuery) => {
		return await api.exportData(query);
	};

	return {
		crudOptions: {
			request: {
				pageRequest,
				addRequest,
				editRequest,
				delRequest,
			},
			actionbar: {
				buttons: {
					export: {
						// 注释编号:django-vue3-admin-crud210716:注意这个auth里面的值,最好是使用index.vue文件里面的name值并加上请求动作的单词
						show: auth('CrudDemoModelViewSet:Export'),
						text: '导出', //按钮文字
						title: '导出', //鼠标停留显示的信息
						click() {
							return exportRequest(crudExpose.getSearchFormData());
							// return exportRequest(crudExpose!.getSearchFormData())    // 注意这个crudExpose!.getSearchFormData(),一些低版本的环境是需要添加!的
						},
					},
					add: {
						show: auth('CrudDemoModelViewSet:Create'),
					},
				},
			},
			rowHandle: {
				//固定右侧
				fixed: 'right',
				width: 200,
				buttons: {
					view: {
						type: 'text',
						order: 1,
						show: auth('CrudDemoModelViewSet:Retrieve'),
					},
					edit: {
						type: 'text',
						order: 2,
						show: auth('CrudDemoModelViewSet:Update'),
					},
					copy: {
						type: 'text',
						order: 3,
						show: auth('CrudDemoModelViewSet:Copy'),
					},
					remove: {
						type: 'text',
						order: 4,
						show: auth('CrudDemoModelViewSet:Delete'),
					},
				},
			},
			columns: {
				goods: {
					title: '商品',
					type: 'input',
					search: { show: true },
					column: {
						minWidth: 120,
						sortable: 'custom',
					},
					form: {
						helper: {
							render() {
								return <div style={'color:blue'}>商品是必需要填写的</div>;
							},
						},
						rules: [{ required: true, message: '商品名称必填' }],
						component: {
							placeholder: '请输入商品名称',
						},
					},
				},
				inventory: {
					title: '库存量',
					type: 'number',
					search: { show: false },
					column: {
						minWidth: 120,
						sortable: 'custom',
					},
					form: {
						rules: [{ required: true, message: '库存量必填' }],
						component: {
							placeholder: '请输入库存量',
						},
					},
				},

				goods_price: {
					title: '商品定价',
					type: 'text',
					search: { show: false },
					column: {
						minWidth: 120,
						sortable: 'custom',
					},
					form: {
						rules: [{ required: true, message: '商品定价必填' }],
						component: {
							placeholder: '请输入商品定价',
						},
					},
				},
				purchase_goods_date: {
					title: '进货时间',
					type: 'date',
					search: { show: false },
					form: {
						// rules: [{ required: true, message: '进货时间必填' }],
						component: {
							//显示格式化
							format: 'YYYY-MM-DD',
							//输入值格式
							valueFormat: 'YYYY-MM-DD',
							placeholder: '请输入进货时间',
						},
					},
					column: {
						align: 'center',
						width: 120,
						component: { name: 'fs-date-format', format: 'YYYY-MM-DD' },
					},
				},
			},
		},
	};
}

编写index.vue文件

实现标准化页面。这个文件用于定义前端Vue组件。按实际需求修改:

  • 修改<importExcel api="api/CrudDemoModelViewSet/" v-auth="'user:Import'">导入</importExcel>
  • 修改组件名称name: "CrudDemoModelViewSet"
vue
<template>
    <fs-page class="PageFeatureSearchMulti">
        <fs-crud ref="crudRef" v-bind="crudBinding">
            <template #cell_url="scope">
                <el-tag size="small">{{ scope.row.url }}</el-tag>
            </template>
            <!-- 注释编号: django-vue3-admin-index442216: -->
            <!-- 注释编号:django-vue3-admin-index39263917:代码开始行-->
            <!--  功能说明:使用导入组件,并且修改api地址为当前对应的api,当前是demo的api="api/CrudDemoModelViewSet/"-->
            <template #actionbar-right>
                <importExcel api="api/CrudDemoModelViewSet/" v-auth="'user:Import'">导入</importExcel>
            </template>
            <!--  注释编号:django-vue3-admin-index263917:代码结束行-->

        </fs-crud>
    </fs-page>
</template>

<script lang="ts">
import { onMounted, getCurrentInstance, defineComponent } from 'vue';
import { useFs } from '@fast-crud/fast-crud';
import createCrudOptions from './crud';

// 注释编号: django-vue3-admin-index192316:导入组件
import importExcel from '/@/components/importExcel/index.vue'


export default defineComponent({    //这里配置defineComponent
    name: "CrudDemoModelViewSet",   //把name放在这里进行配置了
    components: { importExcel },  //注释编号: django-vue3-admin-index552416: 注册组件,把importExcel组件放在这里,这样<template></template>中才能正确的引用到组件
    setup() {   //这里配置了setup()

        const instance = getCurrentInstance();

        const context: any = {
            componentName: instance?.type.name
        };

        const { crudBinding, crudRef, crudExpose, resetCrudOptions } = useFs({ createCrudOptions, context });


        // 页面打开后获取列表数据
        onMounted(() => {
            crudExpose.doRefresh();
        });
        return {
            //增加了return把需要给上面<template>内调用的<fs-crud ref="crudRef" v-bind="crudBinding">
            crudBinding,
            crudRef,
        };


    } 	//这里关闭setup()
});  //关闭defineComponent

</script>

配置菜单和按钮权限

创建一级菜单

运行项目,以超级管理员登录系统,选择系统管理--菜单管理,点击下方+ 号。填写:

  • 菜单名称:示例:商品管理
  • 父级菜单:留空
  • 图标:选择适当的图标
  • 路由地址:建议不要设置与后端的URL请求地址一样,避免暴露后端接口地址。示例:/goodsManagement
  • 是否目录:是

dvadmin0202

创建二级菜单

运行项目,以超级管理员登录系统,选择系统管理--菜单管理,点击下方+ 号。填写:

  • 菜单名称:示例:商品信息
  • 父级菜单:选择上级菜单。示例:商品管理
  • 路由地址:先填写父级菜单路由,再填写本级路由。示例:/goodsManagement/goodsList
  • 图标:选择适当的图标
  • 是否目录:否
  • 组件地址:等于前面编写的index.vue文件与views目录的相对路径。示例:MyDemo/CrudDemoModelViewSet/index。注意:这里填写之后会自动弹出选项,请选择弹出的项目。
  • 组件名称:等于前面编写的index.vue文件中定义的组件名称,即defineComponent()函数定义的name属性值。示例:CrudDemoModelViewSet

dvadmin0203

配置按钮权限

运行项目,以超级管理员登录系统,选择系统管理--菜单管理,选择菜单“商品管理--商品信息”,右侧按钮权限配置中,点击添加按钮,依次添加如下权限。

序号权限名称权限值请求方式接口地址
1新增CrudDemoModelViewSet:CreatePOST/api/CrudDemoModelViewSet
2编辑CrudDemoModelViewSet:UpdatePUT/api/CrudDemoModelViewSet/{id}/
3删除CrudDemoModelViewSet:DeleteDELETE/api/CrudDemoModelViewSet/{id}/
4查询CrudDemoModelViewSet:SearchGET/api/CrudDemoModelViewSet
5查看CrudDemoModelViewSet:RetrieveGET/api/CrudDemoModelViewSet/{id}/
6导出CrudDemoModelViewSet:ExportPOST/api/CrudDemoModelViewSet/export_data/
7导入CrudDemoModelViewSet:ImportPOST/api/CrudDemoModelViewSet/import_data/
8复制CrudDemoModelViewSet:CopyPOST/api/CrudDemoModelViewSet

dvadmin0207

测试菜单

退出再重新用超级管理员登录系统,点击刚才添加的菜单:商品管理--商品信息,点击添加。添加成功后会看到商品信息。测试商品的增删改查,正常。

dvadmin0204

排错指南

does not exist错误

错误描述

用户登录系统失败,提示:Users matching query does not exist.: /api/login/

解决方法

  1. 经测试,当输入的用户名不存在时,会报该错误。
  2. 当用户名存在,而密码错误时,提示:账号/密码错误;重试4次后将被锁定~: /api/login/

is not defined错误

错误描述

系统管理--菜单管理,选择某个菜单,右侧按钮权限配置,点击批量生成,提示:name 'Menu' is not defined: /api/system/menu_button/batch_create/

解决方法

  1. 运行后端,打开浏览器输入http://127.0.0.1:8000/登录Swagger,检查API接口信息状态正常。
  2. 根据urls.py文件内容,定位到该接口关联的后端视图层文件django-vue3-admin\backend\dvadmin\system\views\menu_button.py,发现Menu属性未定义。dvadmin0205
  3. 在项目Issue中提交Bug。(2024年9月,该Bug已在develop分支中已修复,等待合并到master分支)

请求地址出错

错误描述

新增菜单并配置按钮权限后,访问该菜单,提示:请求地址出错: /api/CrudDemoModelViewSet/

解决方法

  1. 运行后端,打开浏览器输入http://127.0.0.1:8000/登录Swagger,检查API地址是否正确。
  2. 检查发现地址后面多了一个/dvadmin0208
  3. 进入目录django-vue3-admin\backend\\application,修改文件urls.py。将router.register("api/CrudDemoModelViewSet/", CrudDemoModelViewSet),修改为router.register("api/CrudDemoModelViewSet", CrudDemoModelViewSet)
  4. 重启前后端,测试正常。问题解决。

参考资料

TIP

有任何疑问,欢迎在技术交流区留下您的见解,一起交流成长!