diff --git a/api/generate.py b/api/generate.py new file mode 100644 index 0000000..b457850 --- /dev/null +++ b/api/generate.py @@ -0,0 +1,444 @@ +# _*_ coding : UTF-8 _*_ +# @Time : 2025/02/28 17:28 +# @UpdateTime : 2025/02/28 17:28 +# @Author : sonder +# @File : generate.py +# @Software : PyCharm +# @Comment : 本程序 +import uuid +from datetime import datetime +from typing import Optional + +from fastapi import APIRouter, Path, Query, Depends, Request +from fastapi.responses import JSONResponse +from jinja2 import Environment, FileSystemLoader +from tortoise import Tortoise + +from annotation.auth import Auth +from annotation.log import Log +from config.constant import MYSQL_TO_PYTHON_TYPE, BusinessType +from controller.login import LoginController +from models.generate import GenerateInfo, GenerateColumn +from schemas.common import BaseResponse, DeleteListParams +from schemas.generate import GetTablesListResponse, AddGenerateInfoParams, UpdateGenerateInfoParams, \ + GetGenerateInfoResponse, GetGenerateInfoListResponse +from utils.common import bytes2human +from utils.generate import Generate +from utils.response import Response + +generateAPI = APIRouter( + prefix="/generate", + dependencies=[Depends(LoginController.get_current_user)] +) + + +@generateAPI.get("/tables", response_class=JSONResponse, response_model=GetTablesListResponse, + summary="获取数据库中的所有表信息") +@Log(title="获取数据库中的所有表信息", business_type=BusinessType.SELECT) +@Auth(permission_list=["generate:btn:tables"]) +async def get_database_tables(request: Request): + """ + 获取当前数据库中的所有表信息,包括: + - 表名 + - 表注释 + - 记录行数 + - 数据大小 + - 索引大小 + - 创建时间 + - 最后更新时间(如果支持) + """ + sql = """SELECT TABLE_NAME,TABLE_COMMENT,TABLE_ROWS,DATA_LENGTH,INDEX_LENGTH,CREATE_TIME,UPDATE_TIME FROM information_schema.tables WHERE table_schema = DATABASE();""" + result = await Tortoise.get_connection("default").execute_query_dict(sql) + # 将结果中的键转换为小写 + formatted_result = [{k.lower(): v for k, v in row.items()} for row in result] + for row in formatted_result: + for key, value in row.items(): + if key == "data_length" or key == "index_length": + row[key] = bytes2human(value) + return Response.success(data={ + "page": 1, + "total": len(formatted_result), + "pageSize": 99999, + "result": formatted_result + }) + + +@generateAPI.post("/add", response_class=JSONResponse, response_model=BaseResponse, summary="添加生成表信息") +@Log(title="添加生成表信息", business_type=BusinessType.INSERT) +@Auth(permission_list=["generate:btn:add"]) +async def add_generate_info(request: Request, params: AddGenerateInfoParams): + if await GenerateInfo.get_or_none(table_name=params.table_name, del_flag=1): + return Response.error(msg="该表信息已存在!") + gen = await GenerateInfo.create( + author=params.author, + class_name=params.class_name, + table_comment=params.table_comment, + table_name=params.table_name, + prefix=params.prefix, + remark=params.remark, + permission_id=params.permission_id, + description=params.description + ) + if gen: + sql = """SELECT * FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = %s;""" + result = await Tortoise.get_connection("default").execute_query_dict(sql, [params.table_name]) + # 转换为小写键名 + formatted_result = [{k.lower(): v for k, v in row.items()} for row in result] + formatted_result.sort(key=lambda x: x['ordinal_position']) + for column in formatted_result: + if await GenerateColumn.get_or_none(table_id=gen.id, column_name=column["column_name"], del_flag=1): + continue + await GenerateColumn.create( + table=gen, + index=column["ordinal_position"], + column_name=column["column_name"], + column_comment=column["column_comment"], + column_type=column["column_type"], + python_type=MYSQL_TO_PYTHON_TYPE.get(column["column_type"]) if MYSQL_TO_PYTHON_TYPE.get( + column["column_type"]) else MYSQL_TO_PYTHON_TYPE.get(column["data_type"], "str"), + python_name=column["column_name"].lower(), + is_insert=True, + is_edit=True, + is_list=True, + is_query=True, + is_required=False, + query_way="__icontains", + show_type="input", + is_hide=False, + ) + return Response.success(msg="添加成功!") + return Response.error(msg="添加失败!") + + +@generateAPI.delete("/delete/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="删除生成表信息") +@generateAPI.post("/delete/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="删除生成表信息") +@Log(title="删除生成表信息", business_type=BusinessType.DELETE) +@Auth(permission_list=["generate:btn:delete"]) +async def delete_generate_info(request: Request, id: str = Path(description="生成表信息ID")): + if table := await GenerateInfo.get_or_none(id=id, del_flag=1): + table.del_flag = 0 + await GenerateColumn.filter(table_id=table.id, del_flag=1).update(del_flag=0) + await table.save() + return Response.success(msg="删除成功!") + return Response.error(msg="该生成表信息不存在!") + + +@generateAPI.delete("/deleteList", response_class=JSONResponse, response_model=BaseResponse, + summary="批量删除生成表信息") +@generateAPI.post("/deleteList", response_class=JSONResponse, response_model=BaseResponse, summary="批量删除生成表信息") +@Log(title="批量删除生成表信息", business_type=BusinessType.DELETE) +@Auth(permission_list=["generate:btn:delete"]) +async def delete_generate_info_list(request: Request, params: DeleteListParams): + for id in set(params.ids): + if table := await GenerateInfo.get_or_none(id=id, del_flag=1): + table.del_flag = 0 + await GenerateColumn.filter(table_id=table.id, del_flag=1).update(del_flag=0) + await table.save() + return Response.success(msg="删除成功!") + + +@generateAPI.put("/update/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="更新生成表信息") +@generateAPI.post("/update/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="更新生成表信息") +@Log(title="更新生成表信息", business_type=BusinessType.UPDATE) +@Auth(permission_list=["generate:btn:update"]) +async def update_generate_info( + request: Request, + params: AddGenerateInfoParams, + id: str = Path(description="生成表信息ID") +): + if table := await GenerateInfo.get_or_none(id=id, del_flag=1): + table.author = params.info.author + table.class_name = params.info.class_name + table.permission_id = params.info.permission_id + table.prefix = params.info.prefix + table.remark = params.info.remark + table.table_comment = params.info.table_comment + table.table_name = params.info.table_name + table.description = params.info.description + await table.save() + + return Response.success(msg="更新成功!") + return Response.error(msg="该生成表信息不存在!") + + +@generateAPI.get("/info/{id}", response_class=JSONResponse, response_model=GetGenerateInfoResponse, + summary="获取生成表信息") +@Log(title="获取生成表信息", business_type=BusinessType.SELECT) +@Auth(permission_list=["generate:btn:info"]) +async def get_generate_info(request: Request, id: str = Path(description="生成表信息ID")): + if table := await GenerateInfo.get_or_none(id=id, del_flag=1): + table = { + "id": table.id, + "author": table.author, + "class_name": table.class_name, + "permission_id": table.permission_id, + "prefix": table.prefix, + "remark": table.remark, + "table_comment": table.table_comment, + "table_name": table.table_name, + "description": table.description, + "create_time": table.create_time, + "update_time": table.update_time, + } + columns = await GenerateColumn.filter(table_id=table["id"], del_flag=1).order_by("index").values( + id="id", + table_id="table__id", + table_name="table__table_name", + index="index", + column_comment="column_comment", + column_name="column_name", + column_type="column_type", + python_name="python_name", + python_type="python_type", + query_way="query_way", + show_type="show_type", + is_insert="is_insert", + is_edit="is_edit", + is_list="is_list", + is_query="is_query", + is_required="is_required", + is_hide="is_hide", + create_time="create_time", + update_time="update_time", + ) + table["columns"] = columns + return Response.success(data=table) + return Response.error(msg="该生成表信息不存在!") + + +@generateAPI.put("/updateColumns/{id}", response_class=JSONResponse, response_model=BaseResponse, + summary="更新生成表列信息") +@generateAPI.post("/updateColumns/{id}", response_class=JSONResponse, response_model=BaseResponse, + summary="更新生成表列信息") +@Log(title="更新生成表列信息", business_type=BusinessType.UPDATE) +@Auth(permission_list=["generate:btn:updateColumns"]) +async def update_generate_info_columns( + request: Request, + params: UpdateGenerateInfoParams, + id: str = Path(description="生成表信息ID") +): + if table := await GenerateInfo.get_or_none(id=id, del_flag=1): + for column in params.columns: + if gen_column := await GenerateColumn.get_or_none(id=column.id, table_id=table.id, del_flag=1): + gen_column.column_comment = column.column_comment + gen_column.column_name = column.column_name + gen_column.column_type = column.column_type + gen_column.python_name = column.python_name + gen_column.python_type = column.python_type + gen_column.query_way = column.query_way + gen_column.show_type = column.show_type + gen_column.is_insert = column.is_insert + gen_column.is_edit = column.is_edit + gen_column.is_list = column.is_list + gen_column.is_query = column.is_query + gen_column.is_required = column.is_required + gen_column.is_hide = column.is_hide + await gen_column.save() + return Response.success(msg="更新成功!") + return Response.error(msg="该生成表信息不存在!") + + +@generateAPI.get("/list", response_class=JSONResponse, response_model=GetGenerateInfoListResponse, + summary="查询生成表信息列表") +@Log(title="查询生成表信息列表", business_type=BusinessType.SELECT) +@Auth(permission_list=["generate:btn:list"]) +async def get_generate_info_list( + request: Request, + page: int = Query(default=1, description="页码"), + pageSize: int = Query(default=10, description="每页数量"), + table_comment: Optional[str] = Query(default=None, description="表注释"), + permission_id: Optional[str] = Query(default=None, description="权限ID"), +): + filterArgs = { + f'{k}__contains': v for k, v in { + 'table_comment': table_comment, + 'permission_id': permission_id + }.items() if v + } + result = await GenerateInfo.filter(**filterArgs, del_flag=1).order_by("-create_time").offset( + (page - 1) * pageSize).limit( + pageSize).values( + id="id", + author="author", + class_name="class_name", + permission_id="permission_id", + prefix="prefix", + remark="remark", + table_comment="table_comment", + table_name="table_name", + description="description", + create_time="create_time", + update_time="update_time", + ) + for item in result: + columns = await GenerateColumn.filter(table_id=item["id"], del_flag=1).order_by("index").values( + id="id", + table_id="table__id", + table_name="table__table_name", + index="index", + column_comment="column_comment", + column_name="column_name", + column_type="column_type", + python_name="python_name", + python_type="python_type", + query_way="query_way", + show_type="show_type", + is_insert="is_insert", + is_edit="is_edit", + is_list="is_list", + is_query="is_query", + is_required="is_required", + is_hide="is_hide", + create_time="create_time", + update_time="update_time", + ) + item["columns"] = columns + return Response.success(data={ + "result": result, + "total": await GenerateInfo.filter(**filterArgs, del_flag=1).count(), + "page": page, + "pageSize": pageSize, + }) + + +@generateAPI.get("/code/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="生成代码") +@Log(title="生成代码", business_type=BusinessType.GENCODE) +@Auth(permission_list=["generate:btn:code"]) +async def generate_code( + request: Request, + id: str = Path(description="生成表信息ID") +): + if table := await GenerateInfo.get_or_none(id=id, del_flag=1): + info = { + "author": table.author, + "class_name": table.class_name, + "permission_id": table.permission_id, + "prefix": table.prefix, + "remark": table.remark, + "table_comment": table.table_comment, + "table_name": table.table_name, + "description": table.description, + } + columns = await GenerateColumn.filter(table_id=table.id, del_flag=1).order_by("index").values( + id="id", + table_id="table__id", + table_name="table__table_name", + index="index", + column_comment="column_comment", + column_name="column_name", + column_type="column_type", + python_name="python_name", + python_type="python_type", + query_way="query_way", + show_type="show_type", + is_insert="is_insert", + is_edit="is_edit", + is_list="is_list", + is_query="is_query", + is_required="is_required", + is_hide="is_hide", + create_time="create_time", + update_time="update_time", + ) + + def generate_uuid(): + return str(uuid.uuid4()) + + def current_time(): + return datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") + + comment_env = Environment(loader=FileSystemLoader('templates')) + comment_env.globals["uuid4"] = generate_uuid # 注册到 Jinja2 + comment_env.globals["now"] = current_time + data = Generate.prepare_template_data(info, columns) + model_template = comment_env.get_template('python/model.py.jinja') + model_code = model_template.render(data) + schemas_template = comment_env.get_template('python/schemas.py.jinja') + schemas_code = schemas_template.render(data) + api_py_template = comment_env.get_template('python/api.py.jinja') + api_py_code = api_py_template.render(data) + type_template = comment_env.get_template('typescript/type.d.ts.jinja') + type_code = type_template.render(data) + api_ts_template = comment_env.get_template('typescript/api.ts.jinja') + api_ts_code = api_ts_template.render(data) + hook_template = comment_env.get_template('typescript/hook.tsx.jinja') + hook_code = hook_template.render(data) + vue_env = Environment( + loader=FileSystemLoader('templates/vue'), + block_start_string="[%", # 替换 Jinja2 代码块的起始标记 + block_end_string="%]", # 替换 Jinja2 代码块的结束标记 + variable_start_string="[[", + variable_end_string="]]", + comment_start_string="[#", + comment_end_string="#]" + ) + index_template = vue_env.get_template('index.vue.jinja') + index_code = index_template.render(data) + form_template = vue_env.get_template('form.vue.jinja') + form_code = form_template.render(data) + sql_template = comment_env.get_template('sql.jinja') + sql_code = sql_template.render(data) + data = { + "backend": [ + { + "label": f"/models/{info['class_name'].lower()}.py", + "value": model_code, + "type": "python", + "path": f"/python/models/{info['class_name'].lower()}.py" + }, + { + "label": f"/schemas/{info['class_name'].lower()}.py", + "value": schemas_code, + "type": "python", + "path": f"/python/schemas/{info['class_name'].lower()}.py" + }, + { + "label": f"/api/{info['class_name'].lower()}.py", + "value": api_py_code, + "type": "python", + "path": f"/python/api/{info['class_name'].lower()}.py" + }, + ], + "frontend": [ + { + "label": f"/types/{info['class_name'].lower()}.d.ts", + "value": type_code, + "type": "typescript", + "path": f"/types/{info['class_name'].lower()}.d.ts" + }, + { + "label": f"/api/{info['class_name'].lower()}.ts", + "value": api_ts_code, + "type": "typescript", + "path": f"/api/{info['class_name'].lower()}.ts" + }, + { + "label": f"/{info['class_name'].lower()}/utils/hook.tsx", + "value": hook_code, + "type": "typescript", + "path": f"/{info['class_name'].lower()}/utils/hook.tsx" + }, + { + "label": f"/{info['class_name'].lower()}/index.vue", + "value": index_code, + "type": "vue", + "path": f"/{info['class_name'].lower()}/index.vue" + }, + { + "label": f"/{info['class_name'].lower()}/components/form.vue", + "value": form_code, + "type": "vue", + "path": f"/{info['class_name'].lower()}/components/form.vue" + }, + ], + "sql": [ + { + "label": f"{info['table_name']}.sql", + "value": sql_code, + "type": "sql", + "path": f"{info['table_name']}.sql" + } + ] + } + return Response.success(data=data) + return Response.error(msg="生成失败!") diff --git a/app.py b/app.py index 04bde64..88246e2 100644 --- a/app.py +++ b/app.py @@ -23,6 +23,7 @@ from api.permission import permissionAPI from api.role import roleAPI from api.server import serverAPI from api.user import userAPI +from api.generate import generateAPI from config.database import init_db, close_db from config.env import AppConfig from config.get_redis import Redis @@ -87,6 +88,7 @@ api_list = [ {'api': serverAPI, 'tags': ['服务器管理']}, {'api': i18nAPI, 'tags': ['国际化管理']}, {'api': configApi, 'tags': ['配置管理']}, + {'api': generateAPI, 'tags': ['代码生成管理']}, ] for api in api_list: diff --git a/config/constant.py b/config/constant.py index 441ebb4..af38311 100644 --- a/config/constant.py +++ b/config/constant.py @@ -265,3 +265,72 @@ class RedisKeyConfig(Enum): """国际化类型,存储国际化类型及其配置信息。""" SYSTEM_CONFIG = {'key': 'system_config', 'remark': '系统配置信息'} """系统配置信息,存储系统的配置信息。""" + + +# MYSQL类型和 Python类型的映射关系 +MYSQL_TO_PYTHON_TYPE = { + "int": "int", + "bigint": "int", + "smallint": "int", + "tinyint": "int", + "tinyint(1)": "bool", # MySQL 的 tinyint(1) 通常用作布尔值 + "float": "float", + "double": "float", + "decimal": "float", + "char": "str", + "varchar": "str", + "text": "str", + "longtext": "str", + "date": "date", + "datetime": "datetime", + "timestamp": "datetime", + "time": "time", + "json": "dict", + "binary": "bytes", + "varbinary": "bytes", + "blob": "bytes", + "tinyblob": "bytes", + "mediumblob": "bytes", + "longblob": "bytes" +} + +MYSQL_TO_TORTOISE_TYPE = { + # 数值类型 + "tinyint": "BooleanField", # 通常用于布尔值 + "smallint": "IntField", + "mediumint": "IntField", + "int": "IntField", + "integer": "IntField", + "bigint": "BigIntField", + "decimal": "DecimalField", + "numeric": "DecimalField", + "float": "FloatField", + "double": "FloatField", + "real": "FloatField", + + # 字符串类型 + "char": "CharField", + "varchar": "CharField", + "tinytext": "TextField", + "text": "TextField", + "mediumtext": "TextField", + "longtext": "TextField", + + # 日期和时间 + "date": "DateField", + "datetime": "DatetimeField", + "timestamp": "DatetimeField", + "time": "TimeField", + "year": "IntField", + + # 二进制数据 + "binary": "BinaryField", + "varbinary": "BinaryField", + "blob": "BinaryField", + "tinyblob": "BinaryField", + "mediumblob": "BinaryField", + "longblob": "BinaryField", + + # JSON + "json": "JSONField", +} diff --git a/models/__init__.py b/models/__init__.py index d30e264..02ec6f9 100644 --- a/models/__init__.py +++ b/models/__init__.py @@ -9,6 +9,7 @@ from models.config import Config from models.department import Department from models.file import File +from models.generate import GenerateInfo, GenerateColumn from models.i18n import I18n, Locale from models.log import LoginLog, OperationLog from models.permission import Permission @@ -27,5 +28,7 @@ __all__ = [ 'UserRole', 'I18n', 'Locale', - 'Config' + 'Config', + 'GenerateInfo', + 'GenerateColumn' ] diff --git a/models/generate.py b/models/generate.py new file mode 100644 index 0000000..ffa1827 --- /dev/null +++ b/models/generate.py @@ -0,0 +1,54 @@ +# _*_ coding : UTF-8 _*_ +# @Time : 2025/02/21 03:37 +# @UpdateTime : 2025/02/21 03:37 +# @Author : sonder +# @File : generate.py +# @Software : PyCharm +# @Comment : 本程序 +from tortoise import fields + +from models.common import BaseModel + + +class GenerateInfo(BaseModel): + """ + 代码生成表模型 + """ + table_name = fields.CharField(max_length=255, default="", description="表名称", source_field="table_name") + table_comment = fields.CharField(max_length=255, default="", description="表注释", source_field="table_comment") + class_name = fields.CharField(max_length=255, default="", description="类名", source_field="class_name") + author = fields.CharField(max_length=255, default="", description="作者", source_field="author") + remark = fields.TextField(default="", description="备注", null=True, source_field="remark") + permission_id = fields.CharField(max_length=255, default="", description="权限ID", source_field="permission_id") + prefix = fields.CharField(max_length=255, default="", description="api前缀", source_field="prefix") + description = fields.TextField(default="", description="描述", null=True, source_field="description") + + class Meta: + table = "generate_info" + table_description = "代码生成表" + + +class GenerateColumn(BaseModel): + """ + 代码生成列模型 + """ + table = fields.ForeignKeyField("models.GenerateInfo", related_name="columns", description="表", + source_field="table_id") + index = fields.IntField(default=0, description="索引", source_field="index") + column_name = fields.CharField(max_length=255, default="", description="字段名称", source_field="column_name") + column_comment = fields.CharField(max_length=255, default="", description="字段注释", source_field="column_comment") + column_type = fields.CharField(max_length=255, default="", description="字段类型", source_field="column_type") + python_type = fields.CharField(max_length=255, default="", description="python类型", source_field="python_type") + python_name = fields.CharField(max_length=255, default="", description="python名称", source_field="python_name") + is_insert = fields.BooleanField(default=True, description="是否插入", source_field="is_insert") + is_edit = fields.BooleanField(default=True, description="是否编辑", source_field="is_edit") + is_list = fields.BooleanField(default=True, description="是否列表", source_field="is_list") + is_query = fields.BooleanField(default=True, description="是否查询", source_field="is_query") + is_required = fields.BooleanField(default=False, description="是否必填", source_field="is_required") + is_hide = fields.BooleanField(default=False, description="是否隐藏", source_field="is_hide") + query_way = fields.CharField(max_length=255, default="", description="查询方式", source_field="query_way") + show_type = fields.CharField(max_length=255, default="", description="显示类型", source_field="show_type") + + class Meta: + table = "generate_column" + table_description = "代码生成列" diff --git a/schemas/generate.py b/schemas/generate.py new file mode 100644 index 0000000..b49fe79 --- /dev/null +++ b/schemas/generate.py @@ -0,0 +1,136 @@ +# _*_ coding : UTF-8 _*_ +# @Time : 2025/02/28 18:00 +# @UpdateTime : 2025/02/28 18:00 +# @Author : sonder +# @File : generate.py +# @Software : PyCharm +# @Comment : 本程序 +from typing import List + +from pydantic import BaseModel, Field, ConfigDict +from pydantic.alias_generators import to_camel, to_snake + +from schemas.common import BaseResponse, ListQueryResult + + +class TableInfo(BaseModel): + """ + 数据表信息模型 + """ + model_config = ConfigDict(alias_generator=to_snake) + id: str = Field(default="", description="主键") + table_name: str = Field(default="", description="表名称") + table_comment: str = Field(default="", description="表注释") + table_rows: int = Field(default=0, description="表行数") + data_length: str = Field(default="", description="表大小") + index_length: str = Field(default="", description="索引大小") + create_time: str = Field(default="", description="创建时间") + update_time: str = Field(default="", description="更新时间") + + +class GenerateTableInfo(BaseModel): + """ + 生成表信息模型 + """ + model_config = ConfigDict(alias_generator=to_snake) + id: str = Field(default="", description="主键") + table_name: str = Field(default="", description="表名称") + table_comment: str = Field(default="", description="表注释") + author: str = Field(default="", description="作者") + prefix: str = Field(default="", description="api前缀") + class_name: str = Field(default="", description="类名") + remark: str = Field(default="", description="备注") + description: str = Field(default="", description="描述") + permission_id: str = Field(default="", description="权限ID") + create_time: str = Field(default="", description="创建时间") + update_time: str = Field(default="", description="更新时间") + + +class GetTableListResult(ListQueryResult): + """ + 获取数据表结果 + """ + result: List[TableInfo] = Field(default=None, description="响应数据") + + +class GetTablesListResponse(BaseResponse): + """ + 获取数据库表结果 + """ + data: TableInfo = Field(default=None, description="响应数据") + + +class AddGenerateInfoParams(BaseModel): + """ + 添加生成信息参数 + """ + table_name: str = Field(default="", description="表名称") + table_comment: str = Field(default="", description="表注释") + author: str = Field(default="", description="作者") + prefix: str = Field(default="", description="api前缀") + class_name: str = Field(default="", description="类名") + remark: str = Field(default="", description="备注") + permission_id: str = Field(default="", description="权限ID") + description: str = Field(default="", description="备注") + + +class GenerateColumnInfo(BaseModel): + """ + 生成列信息 + """ + model_config = ConfigDict(alias_generator=to_snake) + id: str = Field(default="", description="主键") + table_id: str = Field(default="", description="表ID") + table_name: str = Field(default="", description="表名称") + table_comment: str = Field(default="", description="表注释") + column_name: str = Field(default="", description="字段名称") + column_comment: str = Field(default="", description="字段注释") + column_type: str = Field(default="", description="字段类型") + python_type: str = Field(default="", description="python类型") + python_name: str = Field(default="", description="python名称") + is_insert: bool = Field(default=True, description="是否插入") + is_edit: bool = Field(default=True, description="是否编辑") + is_list: bool = Field(default=True, description="是否列表") + is_query: bool = Field(default=True, description="是否查询") + query_way: str = Field(default="", description="查询方式") + show_type: str = Field(default="", description="显示类型") + is_required: bool = Field(default=False, description="是否必填") + is_hide: bool = Field(default=False, description="是否隐藏") + index: int = Field(default=0, description="索引") + create_time: str = Field(default="", description="创建时间") + update_time: str = Field(default="", description="更新时间") + + +class UpdateGenerateInfoParams(BaseModel): + """ + 更新生成信息参数 + """ + columns: List[GenerateColumnInfo] = Field(default=None, description="生成列信息") + + +class GetGenerateInfoResult(GenerateTableInfo): + """ + 获取生成信息结果 + """ + columns: List[GenerateColumnInfo] = Field(default=None, description="生成列信息") + + +class GetGenerateInfoListResult(ListQueryResult): + """ + 获取生成信息结果 + """ + result: List[GetGenerateInfoResult] = Field(default=[], description="响应数据") + + +class GetGenerateInfoResponse(BaseResponse): + """ + 获取生成信息结果 + """ + data: GetGenerateInfoResult = Field(default=None, description="响应数据") + + +class GetGenerateInfoListResponse(BaseResponse): + """ + 获取生成信息结果 + """ + data: GetGenerateInfoListResult = Field(default=[], description="响应数据") diff --git a/templates/python/api.py.jinja b/templates/python/api.py.jinja new file mode 100644 index 0000000..11d20ee --- /dev/null +++ b/templates/python/api.py.jinja @@ -0,0 +1,135 @@ +# _*_ coding : UTF-8 _*_ +# @Time : {{ current_time }} +# @UpdateTime : {{ current_time }} +# @Author : {{ author }} +# @File : {{ table_name }}.py +# @Comment : 本程序用于生成{{ table_comment }}增删改查接口 + +from datetime import datetime +from typing import Optional + +from fastapi import APIRouter, Depends, Path, Request, Query +from fastapi.responses import JSONResponse + +from annotation.auth import Auth +from annotation.log import Log +from config.constant import BusinessType +from controller.login import LoginController +from models import {{ class_name }} +from schemas.common import BaseResponse, DeleteListParams +from schemas.{{ name }} import Add{{ class_name }}Params, Update{{ class_name }}Params, Get{{ class_name }}InfoResponse, Get{{ class_name }}ListResponse +from utils.response import Response + +{{ table_name }}API= APIRouter( + prefix="{{ prefix }}", + dependencies=[Depends(LoginController.get_current_user)], +) + +@{{ table_name }}API.post("/add", response_class=JSONResponse, response_model=BaseResponse, summary="新增{{ description }}") +@Log(title="新增{{ description }}", business_type=BusinessType.INSERT) +@Auth(permission_list=["{{ name }}:btn:add"]) +async def add_{{ name }}(request: Request, params: Add{{ class_name }}Params): + if await {{ class_name }}.get_or_none( + {% for column in columns if column.is_insert %} + {{ column.python_name }} = params.{{ column.python_name }}, + {% endfor %} + del_flag=1 + ): + return Response.error(msg="{{ description }}已存在!") + {{ name }} = await {{ class_name }}.create( + {% for column in columns if column.is_insert %} + {{ column.python_name }} = params.{{ column.python_name }}, + {% endfor %} + ) + if {{ name }}: + return Response.success(msg="新增成功!") + else: + return Response.error(msg="新增失败") + + +@{{ table_name }}API.delete("/delete/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="删除{{ description }}") +@{{ table_name }}API.post("/delete/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="删除{{ description }}") +@Log(title="删除{{ description }}", business_type=BusinessType.DELETE) +@Auth(permission_list=["{{ name }}:btn:delete"]) +async def delete_{{ name }}(request: Request, id: str = Path(description="{{ description }}ID")): + if {{ name }} := await {{ class_name }}.get_or_none(id=id, del_flag=1): + {{ name }}.del_flag = 0 + await {{ name }}.save() + return Response.success(msg="删除成功") + else: + return Response.error(msg="{{ description }}不存在!") + + +@{{ table_name }}API.delete("/deleteList", response_class=JSONResponse, response_model=BaseResponse, summary="批量删除{{ description }}") +@{{ table_name }}API.post("/deleteList", response_class=JSONResponse, response_model=BaseResponse, summary="批量删除{{ description }}") +@Log(title="批量删除{{ description }}", business_type=BusinessType.DELETE) +@Auth(permission_list=["{{ name }}:btn:delete"]) +async def delete_{{ name }}_list(request: Request, params: DeleteListParams): + for id in set(params.ids): + if {{ name }} := await {{ class_name }}.get_or_none(id=id, del_flag=1): + {{ name }}.del_flag = 0 + await {{ name }}.save() + return Response.success(msg="删除成功") + + +@{{ table_name }}API.put("/update/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="修改{{ description }}") +@{{ table_name }}API.post("/update/{id}", response_class=JSONResponse, response_model=BaseResponse, summary="修改{{ description }}") +@Log(title="修改{{ description }}", business_type=BusinessType.UPDATE) +@Auth(permission_list=["{{ name }}:btn:update"]) +async def update_{{ name }}(request: Request, params: Update{{ class_name }}Params, id: str = Path(description="{{ description }}ID")): + if {{ name }} := await {{ class_name }}.get_or_none(id=id, del_flag=1): + {% for column in columns if column.is_edit %} + {{ name }}.{{ column.python_name }} = params.{{ column.python_name }}, + {% endfor %} + await {{ name }}.save() + return Response.success(msg="修改成功") + else: + return Response.error(msg="{{ description }}不存在") + + +@{{ table_name }}API.get("/info/{id}", response_class=JSONResponse, response_model=Get{{ class_name }}InfoResponse, summary="获取{{ description }}信息") +@Log(title="获取{{ description }}信息", business_type=BusinessType.SELECT) +@Auth(permission_list=["{{ name }}:btn:info"]) +async def get_{{ name }}_info(request: Request, id: str = Path(description="{{ description }}ID")): + if {{ name }} := await {{ class_name }}.get_or_none(id=id, del_flag=1): + data = { + {% for column in columns if column.is_list %} + "{{ column.python_name }}":{{ name }}.{{ column.python_name }}, + {% endfor %} + } + return Response.success(data=data) + else: + return Response.error(msg="{{ description }}不存在") + + +@{{ table_name }}API.get("/list", response_class=JSONResponse, response_model=Get{{ class_name }}ListResponse, summary="获取{{ description }}列表") +@Log(title="获取{{ description }}列表", business_type=BusinessType.SELECT) +@Auth(permission_list=["{{ name }}:btn:list"]) +async def get_{{ name }}_list( + request: Request, + + page: int = Query(default=1, description="当前页码"), + + pageSize: int = Query(default=10, description="每页数量"), + {% for column in columns if column.is_query %} + {{ column.python_name }}: Optional[str] = Query(default=None, description="{{ column.column_comment }}"), + {% endfor %} + ): + filterArgs={ + {% for column in columns if column.is_query %} + "{{ column.python_name }}{{ column.query_way }}": {{ column.python_name }}, + {% endfor %} + } + filterArgs = {k: v for k, v in filterArgs.items() if v is not None} + total = await {{ class_name }}.filter(**filterArgs, del_flag=1).count() + data = await {{ class_name }}.filter(**filterArgs, del_flag=1).offset((page - 1) * pageSize).limit(pageSize).values( + {% for column in columns if column.is_list %} + {{ column.python_name }} = "{{ column.python_name }}", + {% endfor %} + ) + return Response.success(data={ + "total": total, + "result": data, + "page": page, + "pageSize": pageSize, + }) diff --git a/templates/python/model.py.jinja b/templates/python/model.py.jinja new file mode 100644 index 0000000..5aa107c --- /dev/null +++ b/templates/python/model.py.jinja @@ -0,0 +1,44 @@ +# _*_ coding : UTF-8 _*_ +# @Time : {{ current_time }} +# @UpdateTime : {{ current_time }} +# @Author : {{ author }} +# @File : {{ table_name }}.py +# @Comment : 本程序用于{{ table_comment }}模型 + +from tortoise import fields + +from models.common import BaseModel + + +class {{ class_name }}(BaseModel): + """ + {{ table_comment }}模型 + """ + {% for column in columns %} + {%- set params = [] %} + {%- if column.max_length is not none %}{% set params = params + ["max_length=" ~ column.max_length] %}{% endif %} + {%- if column.is_nullable %}{% set params = params + ["null=True"] %}{% endif %} + {%- if column.is_unique %}{% set params = params + ["unique=True"] %}{% endif %} + {%- if column.default is not none %}{% set params = params + ["default=" ~ column.default] %}{% endif %} + {%- if column.column_comment %}{% set params = params + ['description="' ~ column.column_comment ~ '"'] %}{% endif %} + {%- if column.column_name %}{% set params = params + ['source_field="' ~ column.column_name ~ '"'] %}{% endif %} + + {{ column.python_name }} = fields.{{ column.field_type }}({{ params | join(", ") }}) + """ + {{ column.column_comment }}。 + {%- if column.max_length is not none %} + - 最大长度为 {{ column.max_length }} 个字符 + {%- endif %} + - 映射到数据库字段 {{ column.column_name }} + {%- if column.is_nullable %} + - 可为空 + {%- endif %} + {%- if column.default is not none %} + - 默认值:{{ column.default }} + {%- endif %} + """ + {% endfor %} + + class Meta: + table = "{{ table_name }}" + table_description = "{{ table_comment }}" diff --git a/templates/python/schemas.py.jinja b/templates/python/schemas.py.jinja new file mode 100644 index 0000000..c16a785 --- /dev/null +++ b/templates/python/schemas.py.jinja @@ -0,0 +1,51 @@ +# _*_ coding : UTF-8 _*_ +# @Time : {{ current_time }} +# @UpdateTime : {{ current_time }} +# @Author : {{ author }} +# @File : {{ table_name }}.py +# @Comment : 本程序用于生成{{ table_comment }}参数和响应模型 + +from datetime import datetime +from typing import Optional, List +from pydantic import BaseModel, Field, ConfigDict +from pydantic.alias_generators import to_snake +from schemas.common import BaseResponse, ListQueryResult + +class {{ class_name }}Info(BaseModel): + """{{ description }}信息""" + model_config = ConfigDict(alias_generator=to_snake, populate_by_name=True) + {% for column in columns %} + {{ column.python_name }}: {% if not column.is_required %}Optional[{% endif %}{{ column.python_type }}{% if not column.is_required %}]{% endif %} = Field( + {% if column.default is not none %}default={{ column.default }}, {% endif %}title="{{ column.column_comment }}" + ) + {% endfor %} + +class Add{{ class_name }}Params(BaseModel): + """新增{{ description }}参数""" + model_config = ConfigDict(alias_generator=to_snake, populate_by_name=True) + {% for column in columns if column.is_insert %} + {{ column.python_name }}: {% if not column.is_required %}Optional[{% endif %}{{ column.python_type }}{% if not column.is_required %}]{% endif %} = Field( + {% if column.default is not none %}default={{ column.default }}, {% endif %}title="{{ column.column_comment }}" + ) + {% endfor %} + +class Update{{ class_name }}Params(BaseModel): + """更新{{ description }}参数""" + model_config = ConfigDict(alias_generator=to_snake, populate_by_name=True) + {% for column in columns if column.is_edit %} + {{ column.python_name }}: {% if not column.is_required %}Optional[{% endif %}{{ column.python_type }}{% if not column.is_required %}]{% endif %} = Field( + {% if column.default is not none %}default={{ column.default }}, {% endif %}title="{{ column.column_comment }}" + ) + {% endfor %} + +class Get{{ class_name }}InfoResponse(BaseResponse): + """获取{{ description }}信息响应""" + data: {{ class_name }}Info = Field(None, title="{{ table_comment }}信息") + +class Get{{ class_name }}InfoListResult(ListQueryResult): + """获取{{ description }}信息列表响应结果""" + result: List[{{ class_name }}Info] = Field(None, title="{{ table_comment }}信息列表") + +class Get{{ class_name }}InfoListResponse(BaseResponse): + """获取{{ description }}信息列表响应""" + data: Get{{ class_name }}InfoListResult = Field(None, title="{{ table_comment }}信息列表") diff --git a/templates/sql.jinja b/templates/sql.jinja new file mode 100644 index 0000000..bb19544 --- /dev/null +++ b/templates/sql.jinja @@ -0,0 +1,14 @@ +-- {{ description }}权限 + +-- 按钮权限 +-- 添加 +INSERT INTO `permission` VALUES ('{{ uuid4() }}', 1, '', '{{ now() }}', '', '{{ now() }}', 3, '{{ permission_id }}', 'buttons:Add', '', '', '', 1, '', '', '', '', '', '', '{{ name }}:btn:add', '', 1, 0, 0, 0, 1, 0, 0); +-- 删除 +INSERT INTO `permission` VALUES ('{{ uuid4() }}', 1, '', '{{ now() }}', '', '{{ now() }}', 3, '{{ permission_id }}', 'buttons:Delete', '', '', '', 2, '', '', '', '', '', '', '{{ name }}:btn:delete', '', 1, 0, 0, 0, 1, 0, 0); +-- 修改 +INSERT INTO `permission` VALUES ('{{ uuid4() }}', 1, '', '{{ now() }}', '', '{{ now() }}', 3, '{{ permission_id }}', 'buttons:Update', '', '', '', 3, '', '', '', '', '', '', '{{ name }}:btn:update', '', 1, 0, 0, 0, 1, 0, 0); +-- 详情 +INSERT INTO `permission` VALUES ('{{ uuid4() }}', 1, '', '{{ now() }}', '', '{{ now() }}', 3, '{{ permission_id }}', 'buttons:Details', '', '', '', 4, '', '', '', '', '', '', '{{ name }}:btn:info', '', 1, 0, 0, 0, 1, 0, 0); +-- 数据列表 +INSERT INTO `permission` VALUES ('{{ uuid4() }}', 1, '', '{{ now() }}', '', '{{ now() }}', 3, '{{ permission_id }}', 'buttons:DataList', '', '', '', 5, '', '', '', '', '', '', '{{ name }}:btn:list', '', 1, 0, 0, 0, 1, 0, 0); + diff --git a/templates/typescript/api.ts.jinja b/templates/typescript/api.ts.jinja new file mode 100644 index 0000000..ef51203 --- /dev/null +++ b/templates/typescript/api.ts.jinja @@ -0,0 +1,42 @@ +import { http } from "@/utils/http"; +import type { + {{ class_name }}Info, + Get{{ class_name }}ListParams, + Add{{ class_name }}Params, + Update{{ class_name }}Params, +} from "types/{{ name }}"; +import { filterEmptyObject } from "./utils"; + +/** 添加{{ description }}数据 */ +export const postAdd{{ class_name }}API = (data: Add{{ class_name }}Params) => { + return http.request("post", "/api{{ prefix }}/add", { data }); +}; + +/** 删除{{ description }}数据 */ +export const delete{{ class_name }}API = (id: string) => { + return http.request("delete", `/api{{ prefix }}/delete/${id}`); +}; + +/** 批量删除{{ description }}数据 */ +export const delete{{ class_name }}ListAPI = (ids: string[]) => { + return http.request("delete", "/api{{ prefix }}/delete", { + data: { ids }, + }); +}; + +/** 修改{{ description }}数据 */ +export const putUpdate{{ class_name }}API = (data: Update{{ class_name }}Params, id: string) => { + return http.request("put", `/api{{ prefix }}/update/${id}`, { data }); +}; + +/** 获取{{ description }}信息 */ +export const get{{ class_name }}InfoAPI = (id: string) => { + return http.request<{{ class_name }}Info>("get", `/api{{ prefix }}/info/${id}`); +}; + +/** 获取{{ description }}列表 */ +export const get{{ class_name }}ListAPI = (params: Get{{ class_name }}ListParams) => { + return http.request>("get", "/api{{ prefix }}/list", { + params: filterEmptyObject(params), + }); +}; \ No newline at end of file diff --git a/templates/typescript/hook.tsx.jinja b/templates/typescript/hook.tsx.jinja new file mode 100644 index 0000000..571e2da --- /dev/null +++ b/templates/typescript/hook.tsx.jinja @@ -0,0 +1,284 @@ +import dayjs from "dayjs"; +import editForm from "../components/form.vue"; +import { message } from "@/utils/message"; +import { type Ref, ref, reactive, onMounted, h, toRaw } from "vue"; +import type { {{ class_name }}Info } from "types/{{ name }}"; +import type { PaginationProps } from "@pureadmin/table"; +import { addDialog } from "@/components/ReDialog"; +import { + delete{{ class_name }}API, + delete{{ class_name }}ListAPI, + get{{ class_name }}ListAPI, + postAdd{{ class_name }}API, + putUpdate{{ class_name }}API +} from "@/api/{{ name }}"; +import { getKeyList } from "@pureadmin/utils"; + +export const use{{ class_name }} = (tableRef: Ref) => { + /** + * 查询表单 + */ + const form = reactive({ + {% for column in columns if column.is_query %} + /** {{ column.column_comment }} */ + {{ column.column_name }}: "", + {% endfor %} + }); + /** + * 表单Ref + */ + const formRef = ref(null); + /** + * 数据列表 + */ + const dataList = ref<{{ class_name }}Info[]>([]); + /** + * 加载状态 + */ + const loading = ref(true); + /** + * 已选数量 + */ + const selectedNum = ref(0); + /** + * 分页参数 + */ + const pagination = reactive({ + total: 0, + pageSize: 10, + currentPage: 1, + background: true, + pageSizes: [10, 20, 30, 40, 50] + }); + + /** + * 表格列设置 + */ + const columns: TableColumnList = [ + { + label: "勾选列", // 如果需要表格多选,此处label必须设置 + type: "selection", + fixed: "left", + reserveSelection: true // 数据刷新后保留选项 + }, + {% for column in columns if column.is_list %} + { + label: "{{ column.column_comment }}", + prop: "{{ column.column_name }}", + hide: {{ "true" if column.is_hide else "false" }}{% if column.python_type == "datetime" %}, + formatter: ({ {{ column.column_name }} }) => + dayjs({{ column.column_name }}).format("YYYY-MM-DD HH:mm:ss"){% endif %} + }, + {% endfor %} + { + label: "操作", + fixed: "right", + width: 220, + slot: "operation" + } + ]; + /** + * 初次查询 + */ + const onSearch = async () => { + loading.value = true; + const res = await get{{ class_name }}ListAPI({ + page: pagination.currentPage, + pageSize: pagination.pageSize, + ...toRaw(form) + }); + if (res.success) { + dataList.value = res.data.result; + pagination.total = res.data.total; + pagination.currentPage = res.data.page; + pagination.pageSize = res.data.pageSize; + } + message(res.msg, { + type: res.success ? "success" : "error" + }); + loading.value = false; + }; + /** + * 重置表单 + * @param formEl 表单ref + * @returns + */ + const resetForm = async (formEl: any) => { + if (!formEl) return; + formEl.resetFields(); + await onSearch(); + }; + /** + * 处理删除 + * @param row + */ + const handleDelete = async (row: {{ class_name }}Info) => { + const res = await delete{{ class_name }}API(row.id); + if (res.success) { + onSearch(); + } + message(res.msg, { + type: res.success ? "success" : "error" + }); + }; + /** + * 处理每页数量变化 + */ + const handleSizeChange = async (val: number) => { + loading.value = true; + const res = await get{{ class_name }}ListAPI({ + page: pagination.currentPage, + pageSize: val, + ...toRaw(form) + }); + if (res.success) { + dataList.value = res.data.result; + pagination.total = res.data.total; + pagination.currentPage = res.data.page; + pagination.pageSize = res.data.pageSize; + } + message(res.msg, { + type: res.success ? "success" : "error" + }); + loading.value = false; + }; + + /** + * 处理页码变化 + * @param val + */ + const handleCurrentChange = async (val: number) => { + loading.value = true; + const res = await get{{ class_name }}ListAPI({ + page: val, + pageSize: pagination.pageSize, + ...toRaw(form) + }); + if (res.success) { + dataList.value = res.data.result; + pagination.total = res.data.total; + pagination.currentPage = res.data.page; + pagination.pageSize = res.data.pageSize; + } + message(res.msg, { + type: res.success ? "success" : "error" + }); + loading.value = false; + }; + /** 当CheckBox选择项发生变化时会触发该事件 */ + const handleSelectionChange = async (val: any) => { + selectedNum.value = val.length; + // 重置表格高度 + tableRef.value.setAdaptive(); + }; + + /** 取消选择 */ + const onSelectionCancel = async () => { + selectedNum.value = 0; + // 用于多选表格,清空用户的选择 + tableRef.value.getTableRef().clearSelection(); + }; + /** + * 批量删除 + */ + const onbatchDel = async () => { + // 返回当前选中的行 + const curSelected = tableRef.value.getTableRef().getSelectionRows(); + const res = await delete{{ class_name }}ListAPI({ + ids: getKeyList(curSelected, "id") + }); + if (res.success) { + message(res.msg, { + type: "success" + }); + tableRef.value.getTableRef().clearSelection(); + onSearch(); + } else { + message(res.msg, { type: "error", duration: 5000 }); + } + }; + const openDialog = async (title = "新增", row?: {{ class_name }}Info) => { + addDialog({ + title: `${title}配置`, + props: { + formInline: { + /** 方式 */ + title:title, + {% for column in columns if column.is_list %} + /** {{ column.column_comment }} */ + {{ column.python_name }}: row?.{{ column.python_name }} ?? "", + {% endfor %} + } + }, + width: "45%", + draggable: true, + fullscreenIcon: true, + closeOnClickModal: false, + contentRenderer: () => + h(editForm, { + formInline: { + /** 方式 */ + title:title, + {% for column in columns if column.is_list %} + /** {{ column.column_comment }} */ + {{ column.python_name }}: row?.{{ column.python_name }} ?? "", + {% endfor %} + }, + ref: formRef + }), + beforeSure: async (done, {}) => { + const FormData = formRef.value.newFormInline; + if (title === "新增") { + let addForm = { + {% for column in columns if column.is_insert %} + /** {{ column.column_comment }} */ + {{ column.python_name }}: FormData.{{ column.python_name }} ?? "", + {% endfor %} + }; + const res = await postAdd{{ class_name }}PI(addForm); + if (res.success) { + done(); + await onSearch(); + } + message(res.msg, { type: res.success ? "success" : "error" }); + } else { + let updateForm = { + {% for column in columns if column.is_update %} + /** {{ column.column_comment }} */ + {{ column.python_name }}: FormData.{{ column.python_name }} ?? "", + {% endfor %} + }; + const res = await putUpdate{{ class_name }}API(updateForm, row.id); + if (res.success) { + done(); + await onSearch(); + } + message(res.msg, { type: res.success ? "success" : "error" }); + } + } + }); + }; + /** + * 页面加载执行 + */ + onMounted(async () => { + await onSearch(); + }); + return { + form, + dataList, + loading, + pagination, + columns, + selectedNum, + openDialog, + onSearch, + resetForm, + handleDelete, + handleSizeChange, + handleCurrentChange, + handleSelectionChange, + onSelectionCancel, + onbatchDel + }; +}; diff --git a/templates/typescript/type.d.ts.jinja b/templates/typescript/type.d.ts.jinja new file mode 100644 index 0000000..d2f0509 --- /dev/null +++ b/templates/typescript/type.d.ts.jinja @@ -0,0 +1,37 @@ +/** {{ description }}信息 */ +export interface {{ class_name }}Info { + {% for column in columns if column.is_list %} + /** {{ column.column_comment }} */ + {{ column.python_name }}: {{ column.typescript_type }}; + {% endfor %} +} + +/** 获取{{ description }}列表参数 */ +export interface Get{{ class_name }}ListParams { + + /** 当前页 */ + page: number; + + /** 每页数量 */ + pageSize: number; + {% for column in columns if column.is_query %} + /** {{ column.column_comment }} */ + {{ column.python_name }}?: string; + {% endfor %} +} + +/** 添加{{ description }}数据参数 */ +export interface Add{{ class_name }}Params { + {% for column in columns if column.is_insert %} + /** {{ column.column_comment }} */ + {{ column.python_name }}{% if not column.is_required %}?{% endif %}: {{ column.typescript_type }}; + {% endfor %} +} + +/** 更新{{ description }}数据参数 */ +export interface Update{{ class_name }}Params { + {% for column in columns if column.is_edit %} + /** {{ column.column_comment }} */ + {{ column.python_name }}{% if not column.is_required %}?{% endif %}: {{ column.typescript_type }}; + {% endfor %} +} \ No newline at end of file diff --git a/templates/vue/form.vue.jinja b/templates/vue/form.vue.jinja new file mode 100644 index 0000000..812eae4 --- /dev/null +++ b/templates/vue/form.vue.jinja @@ -0,0 +1,95 @@ + + diff --git a/templates/vue/index.vue.jinja b/templates/vue/index.vue.jinja new file mode 100644 index 0000000..8488fc4 --- /dev/null +++ b/templates/vue/index.vue.jinja @@ -0,0 +1,237 @@ + + + + + diff --git a/utils/generate.py b/utils/generate.py new file mode 100644 index 0000000..1c8d6b8 --- /dev/null +++ b/utils/generate.py @@ -0,0 +1,110 @@ +# _*_ coding : UTF-8 _*_ +# @Time : 2025/03/03 00:03 +# @UpdateTime : 2025/03/03 00:03 +# @Author : sonder +# @File : generate.py +# @Software : PyCharm +# @Comment : 本程序 +from datetime import datetime + +from config.constant import MYSQL_TO_TORTOISE_TYPE + + +class Generate: + """ + 代码生成成工具 + """ + + @classmethod + def convert_mysql_type(cls, mysql_type: str): + """ + 将 MySQL 字段类型转换为 Tortoise-ORM 的字段类型。 + + :param mysql_type: MySQL 的字段类型,如 "varchar(255)"、"int(11)" 等 + :return: Tortoise-ORM 对应的字段类型,如 "CharField"、"IntField" 等 + """ + # 提取字段的基本类型(去掉括号及长度信息) + base_type = mysql_type.split("(")[0].lower() + + # 处理特殊情况:tinyint(1) -> BooleanField + if base_type == "tinyint": + if "(1)" in mysql_type: # tinyint(1) 视为 Boolean + return "BooleanField", None + return "IntField", None # 其他 tinyint 作为 IntField 处理 + max_length = None + if "(" in mysql_type and base_type in {"char", "varchar"}: + max_length = int(mysql_type.split("(")[1].split(")")[0]) # 提取最大长度 + + field_type = MYSQL_TO_TORTOISE_TYPE.get(base_type, "TextField") + return field_type, max_length + + @classmethod + def prepare_template_data(cls, table_info, columns): + TYPE_DEFINE = { + "int": 0, + "str": "", + "bool": False, + "float": 0.0, + "datetime": "", + "list": [], + "dict": {}, + "set": set(), + "tuple": (), + "bytes": b"", + "None": 'null', + } + PYTHON_TO_TS = { + "int": "number", + "float": "number", + "str": "string", + "bool": "boolean", + "datetime": "string", + "Optional[int]": "number | null", + "Optional[str]": "string | null", + "Optional[bool]": "boolean | null", + "Optional[float]": "number | null", + "Optional[datetime]": "string | null", + "List[int]": "number[]", + "List[str]": "string[]", + "List[bool]": "boolean[]", + "List[datetime]": "string[]", + "Dict[str, Any]": "Record", + "Any": "any", + } + """组织数据,供 Jinja2 渲染""" + return { + "author": table_info.get('author', ""), + "prefix": table_info["prefix"], + "table_name": table_info["table_name"], + "class_name": table_info["class_name"], + "table_comment": table_info.get("table_comment", ""), + "description": table_info.get("description", ""), + "name": table_info.get('class_name', "").lower(), + "permission_id": table_info.get('permission_id', ""), + "columns": [ + { + "python_name": col["python_name"], + "field_type": cls.convert_mysql_type(col["column_type"])[0], + "max_length": cls.convert_mysql_type(col["column_type"])[1], + "is_nullable": not col["is_required"], + "is_unique": col.get("is_unique", False), + "default": col.get("default", None), + "column_comment": col.get("column_comment", ""), + "column_name": col["column_name"], + "is_required": col.get("is_required", False), + "is_edit": col.get("is_edit", False), + "is_list": col.get("is_list", False), + "is_query": col.get("is_query", False), + "is_insert": col.get("is_insert", False), + "is_hide": col.get("is_hide", False), + "query_way": col.get("query_way", "="), + "show_type": col.get("show_type", "input"), + "python_type": col.get("python_type", "str"), + "define": TYPE_DEFINE.get(col.get("python_type", "str"), None), + "typescript_type": PYTHON_TO_TS.get(col.get("python_type", "str"), "any"), + } + for col in columns + if col["python_name"] and col["column_type"] + ], + "current_time": datetime.now().strftime("%Y/%m/%d %H:%M:%S") + }