Appearance
CRUD功能
开发后端
创建应用
进入目录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
。
开发前端
前端目录结构
进入目录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
- 是否目录:是
创建二级菜单
运行项目,以超级管理员登录系统,选择系统管理--菜单管理,点击下方+ 号。填写:
- 菜单名称:示例:商品信息
- 父级菜单:选择上级菜单。示例:商品管理
- 路由地址:先填写父级菜单路由,再填写本级路由。示例:
/goodsManagement/goodsList
- 图标:选择适当的图标
- 是否目录:否
- 组件地址:等于前面编写的
index.vue
文件与views
目录的相对路径。示例:MyDemo/CrudDemoModelViewSet/index
。注意:这里填写之后会自动弹出选项,请选择弹出的项目。 - 组件名称:等于前面编写的
index.vue
文件中定义的组件名称,即defineComponent()
函数定义的name
属性值。示例:CrudDemoModelViewSet
配置按钮权限
运行项目,以超级管理员登录系统,选择系统管理--菜单管理,选择菜单“商品管理--商品信息”,右侧按钮权限配置中,点击添加按钮,依次添加如下权限。
序号 | 权限名称 | 权限值 | 请求方式 | 接口地址 |
---|---|---|---|---|
1 | 新增 | CrudDemoModelViewSet:Create | POST | /api/CrudDemoModelViewSet |
2 | 编辑 | CrudDemoModelViewSet:Update | PUT | /api/CrudDemoModelViewSet/{id}/ |
3 | 删除 | CrudDemoModelViewSet:Delete | DELETE | /api/CrudDemoModelViewSet/{id}/ |
4 | 查询 | CrudDemoModelViewSet:Search | GET | /api/CrudDemoModelViewSet |
5 | 查看 | CrudDemoModelViewSet:Retrieve | GET | /api/CrudDemoModelViewSet/{id}/ |
6 | 导出 | CrudDemoModelViewSet:Export | POST | /api/CrudDemoModelViewSet/export_data/ |
7 | 导入 | CrudDemoModelViewSet:Import | POST | /api/CrudDemoModelViewSet/import_data/ |
8 | 复制 | CrudDemoModelViewSet:Copy | POST | /api/CrudDemoModelViewSet |
测试菜单
退出再重新用超级管理员登录系统,点击刚才添加的菜单:商品管理--商品信息,点击添加。添加成功后会看到商品信息。测试商品的增删改查,正常。
排错指南
does not exist错误
错误描述
用户登录系统失败,提示:Users matching query does not exist.: /api/login/
解决方法
- 经测试,当输入的用户名不存在时,会报该错误。
- 当用户名存在,而密码错误时,提示:账号/密码错误;重试4次后将被锁定~: /api/login/
is not defined错误
错误描述
系统管理--菜单管理,选择某个菜单,右侧按钮权限配置,点击批量生成,提示:name 'Menu' is not defined: /api/system/menu_button/batch_create/
解决方法
- 运行后端,打开浏览器输入
http://127.0.0.1:8000/
登录Swagger,检查API接口信息状态正常。 - 根据
urls.py
文件内容,定位到该接口关联的后端视图层文件django-vue3-admin\backend\dvadmin\system\views\menu_button.py
,发现Menu
属性未定义。 - 在项目Issue中提交Bug。(2024年9月,该Bug已在develop分支中已修复,等待合并到master分支)
请求地址出错
错误描述
新增菜单并配置按钮权限后,访问该菜单,提示:请求地址出错: /api/CrudDemoModelViewSet/
解决方法
- 运行后端,打开浏览器输入
http://127.0.0.1:8000/
登录Swagger,检查API地址是否正确。 - 检查发现地址后面多了一个
/
。 - 进入目录
django-vue3-admin\backend\\application
,修改文件urls.py
。将router.register("api/CrudDemoModelViewSet/", CrudDemoModelViewSet)
,修改为router.register("api/CrudDemoModelViewSet", CrudDemoModelViewSet)
- 重启前后端,测试正常。问题解决。
参考资料
TIP
有任何疑问,欢迎在技术交流区留下您的见解,一起交流成长!