diff --git a/README.md b/README.md index efa9b4e..18c97df 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ # 已支持从数据库表生成 - [x] SQLModel - [x] Tortoise ORM -- [x] Vue +- [x] Vue、React - [x] FastAPI 增加、删除、分页查询、详情查询、更新接口 # Generate Code diff --git a/dfs_generate/__init__.py b/dfs_generate/__init__.py index 01ef120..6cd38b7 100644 --- a/dfs_generate/__init__.py +++ b/dfs_generate/__init__.py @@ -1 +1 @@ -__version__ = "0.2.6" +__version__ = "0.2.7" diff --git a/dfs_generate/conversion.py b/dfs_generate/conversion.py index 5d0f567..33a1465 100644 --- a/dfs_generate/conversion.py +++ b/dfs_generate/conversion.py @@ -12,6 +12,7 @@ VUE_API_TS, VUE_INDEX_VUE, VUE_CRUD_TS, + REACT_CRUD_TSX ) from dfs_generate.tools import to_pascal, tran, to_snake, to_camel @@ -39,6 +40,13 @@ def _fast_crud_column(column): return fmt +def _antd_crud_column(column): + name = to_camel(column["COLUMN_NAME"]) + title = column["COLUMN_COMMENT"] or name + fmt = f"{{ title: '{title}', dataIndex: '{name}', key: '{name}', supportSearch: true}}" + return fmt + + class Conversion: def __init__(self, table_name, columns, uri): self.table_name = table_name @@ -77,14 +85,14 @@ def schema(self): + 'model_config = {"alias_generator": to_camel, "populate_by_name": True}' ) return ( - "\n".join(imports) - + "\n\n" - + RESPONSE_SCHEMA - + "\n\n" - + head - + "\n" - + "\n".join(fields) - + "\n" + "\n".join(imports) + + "\n\n" + + RESPONSE_SCHEMA + + "\n\n" + + head + + "\n" + + "\n".join(fields) + + "\n" ) def router(self): @@ -98,13 +106,19 @@ def vue_api_ts(self): def vue_crud_ts(self): columns = ( - "{" + ",".join(_fast_crud_column(column) for column in self.columns) + "}" + "{" + ",".join(_fast_crud_column(column) for column in self.columns) + "}" ) return VUE_CRUD_TS % columns def vue_index_vue(self): return VUE_INDEX_VUE % self.table + def react_crud_tsx(self): + columns = ( + "[" + ",".join(_antd_crud_column(column) for column in self.columns) + "]" + ) + return REACT_CRUD_TSX % (self.table, columns) + def gencode(self): return { "model.py": self.model(), @@ -115,6 +129,7 @@ def gencode(self): "api.ts": self.vue_api_ts(), "crud.ts": self.vue_crud_ts(), "index.vue": self.vue_index_vue(), + "react_curd.tsx": self.react_crud_tsx() } @@ -175,7 +190,7 @@ def _sqlmodel_field_repr(column, imports): name = column["COLUMN_NAME"] if kwargs.get("default", "") is None and "func.now" not in kwargs.get( - "sa_column", "" + "sa_column", "" ): imports.add("from typing import Optional") field_type = f"Optional[{_type}]" @@ -281,12 +296,12 @@ def model(self): if " " + field not in fields: fields.append(" " + field) return ( - "\n".join(imports) - + "\n\n" - + head - + "\n" - + "\n".join(fields) - + f"\n{' ' * 4}class Meta:\n{' ' * 8}table='{self.table_name}'" + "\n".join(imports) + + "\n\n" + + head + + "\n" + + "\n".join(fields) + + f"\n{' ' * 4}class Meta:\n{' ' * 8}table='{self.table_name}'" ) def dao(self): diff --git a/dfs_generate/templates.py b/dfs_generate/templates.py index 5652f96..3105a93 100644 --- a/dfs_generate/templates.py +++ b/dfs_generate/templates.py @@ -6,7 +6,7 @@ 支持ORM:[SQLModel](https://sqlmodel.tiangolo.com/)、[Tortoise ORM](https://tortoise.github.io/) -支持前端: [Vue](https://cn.vuejs.org/) +支持前端: [Vue](https://cn.vuejs.org/)、[React](https://react.dev/) """ RESPONSE_SCHEMA = """ @@ -270,6 +270,7 @@ async def delete_${router_name}_by_id(id: int) -> schema.Result[schema.$table]: * dfs-generate 生成FastAPI Tortoise ORM / SQLModel、Vue3 CRUD代码 * dfs-generate Github: https://github.com/zy7y/dfs-generate * Vue CRUD代码基于fast-crud,更多用法请查看其官方文档 http://fast-crud.docmirror.cn/ + * @fast-crud/fast-crud": "^1.21.1" */ import { CreateCrudOptionsProps, CreateCrudOptionsRet, dict } from "@fast-crud/fast-crud"; import { addRequest, delRequest, editRequest, pageRequest } from "./api"; @@ -296,20 +297,18 @@ async def delete_${router_name}_by_id(id: int) -> schema.Result[schema.$table]: * dfs-generate Github: https://github.com/zy7y/dfs-generate * Vue CRUD代码基于fast-crud,更多用法请查看其官方文档 http://fast-crud.docmirror.cn/ */ -import { AddReq, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; +import type { AddReq, DelReq, EditReq, UserPageQuery, UserPageRes } from "@fast-crud/fast-crud"; import axios from "axios"; const url = "http://127.0.1:5000/%s"; export const pageRequest = async (query: UserPageQuery): Promise => { - const { limit, offset } = query.page; - const pageNumber = offset / limit + 1; - const pageSize = limit; + const { currentPage, pageNumber } = query.page; const res = await axios.get(url, { params: { pageNumber, - pageSize, - ...query.query + pageSize: currentPage, + ...query.form } }); return { @@ -379,3 +378,99 @@ async def delete_${router_name}_by_id(id: int) -> schema.Result[schema.$table]: }); """ + +REACT_CRUD_TSX = """ +/** + * dfs-generate 生成FastAPI Tortoise ORM / SQLModel、React CRUD代码 + * dfs-generate Github: https://github.com/zy7y/dfs-generate + * React CRUD代码基于antd-crud,更多用法请查看其官方文档 https://gitee.com/antdadmin/antd-crud + * @codeflex/antd-crud: "^1.0.6", + */ + +import AntdCrud, { Actions, ColumnsConfig } from "@codeflex/antd-crud"; +import { message } from "antd" +import axios from "axios"; +import { useState } from "react"; + +const BASEURL = "http://127.0.0.1:5000/%s" + +const handleUpdate = async (id: number, data: any) => { + const res = await axios.patch(BASEURL + "/" + id, data) + message.success("更新成功") +} +const handleCreate = async (data: any) => { + const res = await axios.post(BASEURL, data) + message.success("创建成功") +} + +const handleDelete = async (id: number) => { + const res = await axios.delete(BASEURL + "/" + id) + message.success("删除成功") +} +export default function () { + + const [state, setState] = useState({ + data: [], + pageNumber: 1, + pageSize: 10, + total: 0, + }) + + const columns: ColumnsConfig = %s + + + const actions: Actions = { + onCreate: async (account) => { + await handleCreate(account) + await getPageData({}) + }, + onDelete: async (account) => { + await handleDelete(account.id) + await getPageData({}) + + }, + onUpdate: async (account) => { + console.log(account, "update") + await handleUpdate(account.id, account) + await getPageData({}) + + }, + onFetchList: async (currentPage, pageSize, totalPage, searchParams, sortKey, sortType) => { + setState({ + ...state, + pageNumber: currentPage, + pageSize + }) + await getPageData({ + pageSize, + pageNumber: currentPage, + ...(searchParams ?? {}), + }) + }, + + onDeleteBatch: async (rows) => { + message.warning("暂不支持批量删除") + }, + + }; + + const getPageData = async (params: {}) => { + const res = await axios.get(BASEURL, {params}) + setState({ + ...res.data + }) + } + + + return ( + + ) +} +""" diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index cb66012..68ee7fe 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,4 +1,8 @@ # Change Log +## Release v0.2.7 (2024-07-14) +- #29 基于[antd-crud](https://gitee.com/antdadmin/antd-crud)生成`React`代码完整管理页面操作 +- 增加`fast-crud`版本说明(需安装指定版本、否则可能出现不兼容) + ## Release v0.2.6 (2024-06-01) - #68 修复每次打开需要配置链接 diff --git a/tests/test_conversion.py b/tests/test_conversion.py index dd8138a..b65b3af 100644 --- a/tests/test_conversion.py +++ b/tests/test_conversion.py @@ -6,6 +6,8 @@ _pydantic_field, _sqlmodel_field_repr, _tortoise_field_repr, + _fast_crud_column, + _antd_crud_column ) # 假设的列数据,用于模拟从数据库获取的信息 @@ -77,8 +79,8 @@ def test_tortoise_conversion_model(tortoise_conversion_fixture): assert "class Users(Model):" in model_code assert 'id = fields.IntField(description="主键ID", pk=True)' in model_code assert ( - 'name = fields.CharField(null=True, max_length=100, description="姓名")' - in model_code + 'name = fields.CharField(null=True, max_length=100, description="姓名")' + in model_code ) @@ -94,11 +96,11 @@ def test_sqlmodel_field_repr(): column = MOCK_COLUMNS[0] # 使用id字段作为测试 imports, field_code = set(), _sqlmodel_field_repr(column, set()) assert ( - 'id: Optional[int] = Field(default=None,primary_key=True,description="主键ID")' - == field_code + 'id: Optional[int] = Field(default=None,primary_key=True,description="主键ID")' + == field_code ) assert ( - "from datetime import datetime" not in imports + "from datetime import datetime" not in imports ) # id字段不应触发默认时间戳逻辑 @@ -107,6 +109,30 @@ def test_tortoise_field_repr(): column = MOCK_COLUMNS[1] field_code = _tortoise_field_repr(column) assert ( - 'name = fields.CharField(null=True, max_length=100, description="姓名")' - == field_code + 'name = fields.CharField(null=True, max_length=100, description="姓名")' + == field_code ) + + +@pytest.mark.parametrize("column_data,expected", [ + ({"COLUMN_NAME": "user_name", "COLUMN_COMMENT": "User's Name"}, + "userName: { title: 'User's Name', type: 'text', search: { show: true }}"), + ({"COLUMN_NAME": "is_active", "COLUMN_COMMENT": ""}, + "isActive: { title: 'isActive', type: 'text', search: { show: true }}"), + ({"COLUMN_NAME": "last_login_dt", "COLUMN_COMMENT": "Last Login Date & Time"}, + "lastLoginDt: { title: 'Last Login Date & Time', type: 'text', search: { show: true }}"), +]) +def test_fast_crud_column(column_data, expected): + assert _fast_crud_column(column_data) == expected + + +@pytest.mark.parametrize("column_data,expected", [ + ({"COLUMN_NAME": "user_name", "COLUMN_COMMENT": "User's Name"}, + "{ title: 'User's Name', dataIndex: 'userName', key: 'userName', supportSearch: true}"), + ({"COLUMN_NAME": "is_active", "COLUMN_COMMENT": ""}, + "{ title: 'isActive', dataIndex: 'isActive', key: 'isActive', supportSearch: true}"), + ({"COLUMN_NAME": "last_login_dt", "COLUMN_COMMENT": "Last Login Date & Time"}, + "{ title: 'Last Login Date & Time', dataIndex: 'lastLoginDt', key: 'lastLoginDt', supportSearch: true}"), +]) +def test_antd_crud_column(column_data, expected): + assert _antd_crud_column(column_data) == expected