Python Django脚手架fu-admin教程:快速开发(前后端模板代码)

效果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

快速开发

#需求

  • 创建一个项目管理app;
  • 字段包含:项目名称、项目编码、项目状态;
  • 功能:包含项目管理的增删改查、导出。

#后端

#1. 创建App

  • 通过命令创建App python manage.py startapp demo

#2. 在fuadmin/setting.py里添加我们的app

DEBUG = locals().get('DEBUG', True)
ALLOWED_HOSTS = locals().get('ALLOWED_HOSTS', ['*'])
DEMO = locals().get('DEMO', False)

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_celery_beat',
    'django_celery_results',
    'system',
    'demo',
]

#3. 创建 models 模型

  • 编写项目管理模型内容 demo/models.py,如下:
from django.db import models
from utils.models import CoreModel


class Demo(CoreModel):
    name = models.CharField(null=False, max_length=64, verbose_name="项目名称", help_text="项目名称")
    code = models.CharField(max_length=32, verbose_name="项目编码", help_text="项目编码")
    status = models.CharField(max_length=64, verbose_name="项目状态", help_text="项目状态")

    class Meta:
        db_table = "Demo"
        verbose_name = '项目演示'
        verbose_name_plural = verbose_name
        ordering = ('-create_datetime',)

#4. 迁移数据库文件

  • 执行迁移命令:
    • python3 manage.py makemigrations demo
    • python3 manage.py migrate demo
  • 迁移成功后,通过数据库可查看到

#5. 创建api、路由接口

  • 创建api.py

fu_ninga应该为fu_ninja

from typing import List
from ninja import Router, ModelSchema, Query, Field
from ninja.pagination import paginate

from demo.models import Demo
from utils.fu_crud import create, delete, update, retrieve, ImportSchema, export_data, import_data
from utils.fu_ninja import MyPagination, FuFilters

router = Router()


# 设置过滤字段
class Filters(FuFilters):
    name: str = Field(None, alias="name")
    code: str = Field(None, alias="code")
    status: int = Field(None, alias="status")
    id: str = Field(None, alias="demo_id")


# 设置请求接收字段
class DemoSchemaIn(ModelSchema):
    class Config:
        model = Demo
        model_fields = ['name', 'code', 'sort', 'status']


# 设置响应字段
class DemoSchemaOut(ModelSchema):
    class Config:
        model = Demo
        model_fields = "__all__"


# 创建Demo
@router.post("/demo", response=DemoSchemaOut)
def create_demo(request, data: DemoSchemaIn):
    demo = create(request, data, Demo)
    return demo


# 删除Demo
@router.delete("/demo/{demo_id}")
def delete_demo(request, demo_id: int):
    delete(demo_id, Demo)
    return {
    
    "success": True}


# 更新Demo
@router.put("/demo/{demo_id}", response=DemoSchemaOut)
def update_demo(request, demo_id: int, data: DemoSchemaIn):
    demo = update(request, demo_id, data, Demo)
    return demo


# 获取Demo
@router.get("/demo", response=List[DemoSchemaOut])
@paginate(MyPagination)
def list_demo(request, filters: Filters = Query(...)):
    qs = retrieve(request, Demo, filters)
    return qs


# 导入
@router.get("/demo/all/export")
def export_demo(request):
    title_dict = {
    
    
        'name': '名称',
        'code': '编码',
        'status': '状态',
        'sort': '排序',
    }
    return export_data(request, Demo, DemoSchemaOut, title_dict)


# 导出
@router.post("/demo/all/import")
def import_demo(request, data: ImportSchema):
    title_dict = {
    
    
        '名称': 'name',
        '编码': 'code',
        '状态': 'status',
        '排序': 'sort',
    }
    return import_data(request, Demo, DemoSchemaIn, data, title_dict)
  • 创建router.py
from ninja import Router
from demo.api import router

demo_router = Router()
demo_router.add_router('/', router, tags=["Demo"])
    

#6. 在 fuadmin/api.py中添加刚才创建的router

api.add_router('/system/', system_router)
api.add_router('/demo/', demo_router)

#7. 功能接口已完成,文档查看: http://127.0.0.1:8000/api/docs(opens new window)

#前端

#1. 创建 api 文件

  • 在目录web/src/views/demo/下创建api.js: 代码如下:
import {
    
     defHttp } from '/@/utils/http/axios';

enum DeptApi {
    
    
  prefix = '/api/demo/demo',
}

export const getList = (params) => {
    
    
  return defHttp.get({
    
     url: DeptApi.prefix, params });
};

/**
 * 保存或更新
 */

export const createOrUpdate = (params, isUpdate) => {
    
    
  if (isUpdate) {
    
    
    return defHttp.put({
    
     url: DeptApi.prefix + '/' + params.id, params });
  } else {
    
    
    return defHttp.post({
    
     url: DeptApi.prefix, params });
  }
};

export const importData = (params) => {
    
    
  return defHttp.post({
    
     url: DeptApi.prefix + '/all/import', params });
};

export const exportData = () => {
    
    
  return defHttp.get(
          {
    
     url: DeptApi.prefix + '/all/export', responseType: 'blob' },
          {
    
     isReturnNativeResponse: true },
  );
};

/**
 * 删除
 */

export const deleteItem = (id) => {
    
    
  return defHttp.delete({
    
     url: DeptApi.prefix + '/' + id });
};  

#2. 创建 data 文件

  • 在目录web/src/views/demo/下创建data.js: 代码如下:
import {
    
     BasicColumn } from '/@/components/Table';
import {
    
     FormSchema } from '/@/components/Table';

export const columns: BasicColumn[] = [
  {
    
    
    title: '项目名称',
    dataIndex: 'name',
    width: 200,
  },
  {
    
    
    title: '项目编码',
    dataIndex: 'code',
    width: 180,
  },
  {
    
    
    title: '项目排序',
    dataIndex: 'sort',
    width: 100,
  },
  {
    
    
    title: '项目状态',
    dataIndex: 'status',
    width: 100,
  },
  {
    
    
    title: '创建时间',
    dataIndex: 'create_datetime',
    width: 180,
  },
];

export const searchFormSchema: FormSchema[] = [
  {
    
    
    field: 'name',
    label: '项目名称',
    component: 'Input',
    colProps: {
    
     span: 6 },
  },
];

export const formSchema: FormSchema[] = [
  {
    
    
    field: 'id',
    label: 'id',
    component: 'Input',
    show: false,
  },
  {
    
    
    field: 'name',
    label: '项目名称',
    required: true,
    component: 'Input',
  },
  {
    
    
    field: 'code',
    label: '项目编码',
    required: true,
    component: 'Input',
  },
  {
    
    
    field: 'status',
    component: 'DictSelect',
    label: '项目状态',
    componentProps: {
    
    
      dictCode: 'project_status',
    },
  },
  {
    
    
    field: 'sort',
    label: '岗位排序',
    component: 'InputNumber',
    required: true,
  },
];

#3. 创建 index 文件

  • 在目录web/src/views/demo/下创建index.vue: 代码如下:
<template>
  <div>
    <BasicTable @register="registerTable">
      <template #tableTitle>
        <Space style="height: 40px">
          <a-button
                  type="primary"
                  v-auth="['demo:add']"
                  preIcon="ant-design:plus-outlined"
                  @click="handleCreate"
          >
            新增
          </a-button>
          <a-button
                  type="error"
                  v-auth="['demo:delete']"
                  preIcon="ant-design:delete-outlined"
                  @click="handleBulkDelete"
          >
            删除
          </a-button>
          <BasicUpload
                  :maxSize="20"
                  :maxNumber="1"
                  @change="handleChange"
                  class="my-5"
                  type="warning"
                  text="导入"
                  v-auth="['demo:update']"
          />
          <a-button
                  type="success"
                  v-auth="['demo:update']"
                  preIcon="carbon:cloud-download"
                  @click="handleExportData"
          >
            导出
          </a-button>
        </Space>
      </template>
      <template #action="{ record }">
        <TableAction
                :actions="[
            {
    
    
              type: 'button',
              icon: 'clarity:note-edit-line',
              color: 'primary',
              auth: ['demo:update'],
              onClick: handleEdit.bind(null, record),
            },
            {
    
    
              icon: 'ant-design:delete-outlined',
              type: 'button',
              color: 'error',
              placement: 'left',
              auth: ['demo:delete'],
              popConfirm: {
    
    
                title: '是否确认删除',
                confirm: handleDelete.bind(null, record.id),
              },
            },
          ]"
        />
      </template>
    </BasicTable>
    <DemoDrawer @register="registerDrawer" @success="handleSuccess" />
  </div>
</template>
<script lang="ts">
import {
    
     defineComponent } from 'vue';

import {
    
     BasicTable, useTable, TableAction } from '/@/components/Table';
import {
    
     usePermission } from '/@/hooks/web/usePermission';
import {
    
     useDrawer } from '/@/components/Drawer';
import DemoDrawer from './Drawer.vue';
import {
    
     Space } from 'ant-design-vue';
import {
    
     BasicUpload } from '/@/components/Upload';
import {
    
     deleteItem, getList, exportData, importData } from './api';
import {
    
     columns, searchFormSchema } from './data';
import {
    
     message } from 'ant-design-vue';
import {
    
     useMessage } from '/@/hooks/web/useMessage';
import {
    
     downloadByData } from '/@/utils/file/download';
export default defineComponent({
    
    
  name: 'Demo',
  components: {
    
     BasicTable, DemoDrawer, TableAction, BasicUpload, Space },
  setup() {
    
    
    const [registerDrawer, {
    
     openDrawer }] = useDrawer();
    const {
    
     createConfirm } = useMessage();
    const {
    
     hasPermission } = usePermission();
    const [registerTable, {
    
     reload, getSelectRows }] = useTable({
    
    
      api: getList,
      columns,
      formConfig: {
    
    
        labelWidth: 80,
        schemas: searchFormSchema,
      },
      useSearchForm: true,
      showTableSetting: true,
      tableSetting: {
    
     fullScreen: true },
      bordered: true,
      showIndexColumn: false,
      rowSelection: {
    
    
        type: 'checkbox',
      },
      actionColumn: {
    
    
        width: 150,
        title: '操作',
        dataIndex: 'action',
        slots: {
    
     customRender: 'action' },
        fixed: undefined,
      },
    });

    function handleCreate() {
    
    
      openDrawer(true, {
    
    
        isUpdate: false,
      });
    }

    function handleEdit(record: Recordable) {
    
    
      openDrawer(true, {
    
    
        record,
        isUpdate: true,
      });
    }

    async function handleDelete(id: number) {
    
    
      await deleteItem(id);
      message.success('删除成功');
      await reload();
    }

    function handleBulkDelete() {
    
    
      if (getSelectRows().length == 0) {
    
    
        message.warning('请选择一个选项');
      } else {
    
    
        createConfirm({
    
    
          iconType: 'warning',
          title: '提示',
          content: '是否确认删除?',
          async onOk() {
    
    
            for (const item of getSelectRows()) {
    
    
              await deleteItem(item.id);
            }
            message.success('删除成功');
            await reload();
          },
        });
      }
    }

    async function handleChange(list: string[]) {
    
    
      console.log(list[0]);
      await importData({
    
     path: list[0] });
      message.success(`导入成功`);
      await reload();
    }

    async function handleExportData() {
    
    
      const response = await exportData();
      await downloadByData(response.data, '项目数据.xlsx');
    }

    function handleSuccess() {
    
    
      message.success('请求成功');
      reload();
    }

    return {
    
    
      registerTable,
      registerDrawer,
      handleCreate,
      handleEdit,
      handleDelete,
      handleSuccess,
      hasPermission,
      handleBulkDelete,
      getSelectRows,
      handleExportData,
      handleChange,
    };
  },
});
</script> 

#4. 创建 demo 文件

  • 在目录web/src/views/demo/下创建Drawer.vue: 代码如下:
<template>
  <BasicDrawer
          v-bind="$attrs"
          @register="registerDrawer"
          showFooter
          :title="getTitle"
          width="50%"
          @ok="handleSubmit"
  >
    <BasicForm @register="registerForm" />
  </BasicDrawer>
</template>
<script lang="ts">
import {
    
     defineComponent, ref, computed, unref } from 'vue';
import {
    
     BasicForm, useForm } from '/@/components/Form/index';
import {
    
     BasicDrawer, useDrawerInner } from '/@/components/Drawer';
import {
    
     createOrUpdate } from './api';
import {
    
     formSchema } from './data';

export default defineComponent({
    
    
  name: 'ButtonDrawer',
  components: {
    
     BasicDrawer, BasicForm },
  emits: ['success', 'register'],
  setup(_, {
     
      emit }) {
    
    
    const isUpdate = ref(true);

    const [registerForm, {
    
     resetFields, setFieldsValue, validate }] = useForm({
    
    
      labelWidth: 100,
      schemas: formSchema,
      showActionButtonGroup: false,
      baseColProps: {
    
     lg: 12, md: 24 },
    });

    const [registerDrawer, {
    
     setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
    
    
      await resetFields();
      setDrawerProps({
    
     confirmLoading: false });
      isUpdate.value = !!data?.isUpdate;

      if (unref(isUpdate)) {
    
    
        await setFieldsValue({
    
    
          ...data.record,
        });
      }
    });

    const getTitle = computed(() => (!unref(isUpdate) ? '新增项目' : '编辑项目'));

    async function handleSubmit() {
    
    
      try {
    
    
        const values = await validate();
        setDrawerProps({
    
     confirmLoading: true });
        await createOrUpdate(values, unref(isUpdate));
        closeDrawer();
        emit('success');
      } finally {
    
    
        setDrawerProps({
    
     confirmLoading: false });
      }
    }

    return {
    
    
      registerDrawer,
      registerForm,
      getTitle,
      handleSubmit,
    };
  },
});
</script>

#5. 在菜单中添加demo

img

img

#6. 在菜单中添加按钮权限

img

#完成

  • 刷新页面打开 项目演示,则是一个简单完整的 CRUD 完成。

猜你喜欢

转载自blog.csdn.net/a772304419/article/details/133556015