feat: 添加代码生成功能

This commit is contained in:
2025-03-04 00:54:33 +08:00
parent c790233aee
commit bd13f1cfdc
16 changed files with 1758 additions and 1 deletions

444
api/generate.py Normal file
View File

@@ -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="生成失败!")

2
app.py
View File

@@ -23,6 +23,7 @@ from api.permission import permissionAPI
from api.role import roleAPI from api.role import roleAPI
from api.server import serverAPI from api.server import serverAPI
from api.user import userAPI from api.user import userAPI
from api.generate import generateAPI
from config.database import init_db, close_db from config.database import init_db, close_db
from config.env import AppConfig from config.env import AppConfig
from config.get_redis import Redis from config.get_redis import Redis
@@ -87,6 +88,7 @@ api_list = [
{'api': serverAPI, 'tags': ['服务器管理']}, {'api': serverAPI, 'tags': ['服务器管理']},
{'api': i18nAPI, 'tags': ['国际化管理']}, {'api': i18nAPI, 'tags': ['国际化管理']},
{'api': configApi, 'tags': ['配置管理']}, {'api': configApi, 'tags': ['配置管理']},
{'api': generateAPI, 'tags': ['代码生成管理']},
] ]
for api in api_list: for api in api_list:

View File

@@ -265,3 +265,72 @@ class RedisKeyConfig(Enum):
"""国际化类型,存储国际化类型及其配置信息。""" """国际化类型,存储国际化类型及其配置信息。"""
SYSTEM_CONFIG = {'key': 'system_config', 'remark': '系统配置信息'} 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",
}

View File

@@ -9,6 +9,7 @@
from models.config import Config from models.config import Config
from models.department import Department from models.department import Department
from models.file import File from models.file import File
from models.generate import GenerateInfo, GenerateColumn
from models.i18n import I18n, Locale from models.i18n import I18n, Locale
from models.log import LoginLog, OperationLog from models.log import LoginLog, OperationLog
from models.permission import Permission from models.permission import Permission
@@ -27,5 +28,7 @@ __all__ = [
'UserRole', 'UserRole',
'I18n', 'I18n',
'Locale', 'Locale',
'Config' 'Config',
'GenerateInfo',
'GenerateColumn'
] ]

54
models/generate.py Normal file
View File

@@ -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 = "代码生成列"

136
schemas/generate.py Normal file
View File

@@ -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="响应数据")

View File

@@ -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,
})

View File

@@ -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 }}"

View File

@@ -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 }}信息列表")

14
templates/sql.jinja Normal file
View File

@@ -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);

View File

@@ -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<null>("post", "/api{{ prefix }}/add", { data });
};
/** 删除{{ description }}数据 */
export const delete{{ class_name }}API = (id: string) => {
return http.request<null>("delete", `/api{{ prefix }}/delete/${id}`);
};
/** 批量删除{{ description }}数据 */
export const delete{{ class_name }}ListAPI = (ids: string[]) => {
return http.request<null>("delete", "/api{{ prefix }}/delete", {
data: { ids },
});
};
/** 修改{{ description }}数据 */
export const putUpdate{{ class_name }}API = (data: Update{{ class_name }}Params, id: string) => {
return http.request<null>("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<QueryListResult<{{ class_name }}Info>>("get", "/api{{ prefix }}/list", {
params: filterEmptyObject(params),
});
};

View File

@@ -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<number>(0);
/**
* 分页参数
*/
const pagination = reactive<PaginationProps>({
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
};
};

View File

@@ -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 %}
}

View File

@@ -0,0 +1,95 @@
<template>
<el-form ref="ruleFormRef" :model="newFormInline" label-width="82px">
<el-row :gutter="30">
[% for column in columns if column.is_insert or column.is_edit %]
<re-col :value="24" :xm="24" :sm="24">
<el-form-item label="[[ column.column_comment ]]" prop="[[ column.python_name ]]">
[% if column.show_type == "input" %]
<el-input
v-model="newFormInline.[[ column.python_name ]]"
placeholder="请输入[[ column.column_comment ]]~"
clearable
/>
[% elif column.show_type == "textarea" %]
<el-input
v-model="newFormInline.[[ column.python_name ]]"
type="textarea"
placeholder="请输入[[ column.column_comment ]]~"
clearable
/>
[% elif column.show_type == "select" %]
<el-select
v-model="newFormInline.[[ column.python_name ]]"
placeholder="请选择[[ column.column_comment ]]~"
filterable
clearable
>
<el-option
v-for="item in options.[[ column.python_name ]]"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
[% elif column.show_type == "radio" %]
<el-radio-group v-model="newFormInline.[[ column.python_name ]]">
<el-radio
v-for="item in options.[[ column.python_name ]]"
:key="item.value"
:label="item.value"
>{{ item.label }}
</el-radio>
</el-radio-group>
[% elif column.show_type == "checkbox" %]
<el-checkbox-group v-model="newFormInline.[[ column.python_name ]]">
<el-checkbox
v-for="item in options.[[ column.python_name ]]"
:key="item.value"
:label="item.value">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
[% elif column.show_type == "datetime" %]
<el-date-picker
v-model="newFormInline.[[ column.python_name ]]"
type="datetime"
placeholder="请选择[[ column.column_comment ]]~"
format="YYYY-MM-DD HH:mm:ss"
value-format="x" />
[% endif %]
</el-form-item>
</re-col>
[% endfor %]
</el-row>
</el-form>
</template>
<script setup lang="ts">
import { ref } from "vue";
import ReCol from "@/components/ReCol";
import type { FormRules } from "element-plus";
import { [[ class_name ]]Info } from "types/[[ name ]]";
interface FormItemProps {
[% for column in columns if column.is_list %]
/** [[ column.column_comment ]] */
[[ column.python_name ]]: [[ column.typescript_type ]];
[% endfor %]
}
interface FormProps {
formInline: FormItemProps;
}
const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({
[% for column in columns if column.is_list %]
/** [[ column.column_comment ]] */
[[ column.python_name ]]: "",
[% endfor %]
})
});
const newFormInline = ref<ConfigInfo>(props.formInline);
/** 自定义表单规则校验 */
const formRules = reactive<FormRules>({
[% for column in columns if column.is_insert or column.is_edit %]
[[ column.python_name ]]: [{ required: [[ 'true' if column.is_required else 'false' ]], message: "请输入[[ column.column_comment ]]~", trigger: "blur" }],
[% endfor %]
});
defineExpose({ newFormInline });
</script>

View File

@@ -0,0 +1,237 @@
<template>
<div class="main">
<el-form
ref="formRef"
:inline="true"
:model="form"
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px]"
>
[% for column in columns if column.is_query %]
<el-form-item label="[[ column.column_comment ]]" prop="[[ column.python_name ]]">
[% if column.show_type == "input" %]
<el-input
v-model="form.[[ column.python_name ]]"
placeholder="请输入[[ column.column_comment ]]~"
class="!w-[200px]"
clearable
/>
[% elif column.show_type == "textarea" %]
<el-input
v-model="form.[[ column.python_name ]]"
type="textarea"
placeholder="请输入[[ column.column_comment ]]~"
class="!w-[200px]"
clearable
/>
[% elif column.show_type == "select" %]
<el-select
v-model="form.[[ column.python_name ]]"
placeholder="请选择[[ column.column_comment ]]~"
class="!w-[200px]"
clearable
>
<el-option
v-for="item in options.[[ column.python_name ]]"
:key="item.value"
:label="item.label"
:value="item.value" />
</el-select>
[% elif column.show_type == "radio" %]
<el-radio-group v-model="form.[[ column.python_name ]]">
<el-radio
v-for="item in options.[[ column.python_name ]]"
:key="item.value"
:label="item.value"
>{{ item.label }}
</el-radio>
</el-radio-group>
[% elif column.show_type == "checkbox" %]
<el-checkbox-group v-model="form.[[ column.python_name ]]">
<el-checkbox
v-for="item in options.[[ column.python_name ]]"
:key="item.value"
:label="item.value">
{{ item.label }}
</el-checkbox>
</el-checkbox-group>
[% elif column.show_type == "datetime" %]
<el-date-picker
v-model="form.[[ column.python_name ]]"
type="datetime"
placeholder="请选择[[ column.column_comment ]]~"
format="YYYY-MM-DD HH:mm:ss"
value-format="x" />
[% endif %]
</el-form-item>
[% endfor %]
<el-form-item>
<el-button
type="primary"
:icon="useRenderIcon('ri:search-line')"
:loading="loading"
@click="onSearch"
>
{{ t("buttons:Search") }}
</el-button>
<el-button :icon="useRenderIcon(Refresh)" @click="resetForm(formRef)">
{{ t("buttons:Reset") }}
</el-button>
</el-form-item>
</el-form>
<PureTableBar title="{{ description }}管理" :columns="columns" @refresh="onSearch">
<template #buttons>
<el-button
v-if="hasAuth('[[ name ]]:btn:add')"
type="primary"
:icon="useRenderIcon(AddFill)"
@click="openDialog('新增')"
>
{{ t("buttons:Add") }}
</el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<div
v-if="selectedNum > 0"
v-motion-fade
class="bg-[var(--el-fill-color-light)] w-full h-[46px] mb-2 pl-4 flex items-center"
>
<div class="flex-auto">
<span
style="font-size: var(--el-font-size-base)"
class="text-[rgba(42,46,54,0.5)] dark:text-[rgba(220,220,242,0.5)]"
>
已选 {{ selectedNum }} 项
</span>
<el-button type="primary" text @click="onSelectionCancel">
{{ t("buttons:Deselect") }}
</el-button>
</div>
<el-popconfirm
v-if="hasAuth('[[ name ]]:btn:delete')"
title="是否确认删除?"
@confirm="onbatchDel"
>
<template #reference>
<el-button type="danger" text class="mr-1">
{{ t("buttons:DeleteInBatches") }}
</el-button>
</template>
</el-popconfirm>
</div>
<pure-table
ref="tableRef"
row-key="id"
adaptive
border
stripe
:adaptiveConfig="{ offsetBottom: 45 }"
align-whole="center"
table-layout="auto"
:loading="loading"
:size="size"
:data="dataList"
:columns="dynamicColumns"
:pagination="pagination"
:paginationSmall="size === 'small' ? true : false"
:header-cell-style="{
background: 'var(--el-fill-color-light)',
color: 'var(--el-text-color-primary)'
}"
@selection-change="handleSelectionChange"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
>
<template #operation="{ row }">
<el-button
class="reset-margin"
link
type="primary"
:size="size"
:disabled="!hasAuth('[[ name ]]:btn:update')"
:icon="useRenderIcon(EditPen)"
@click="openDialog('修改', row)"
>
{{ t("buttons:Update") }}
</el-button>
<el-popconfirm
:title="`是否确认删除配置名为 ${row.name} 的这条数据`"
@confirm="handleDelete(row)"
>
<template #reference>
<el-button
class="reset-margin"
link
:disabled="!hasAuth('[[ name ]]:btn:delete')"
type="danger"
:size="size"
:icon="useRenderIcon(Delete)"
>
{{ t("buttons:Delete") }}
</el-button>
</template>
</el-popconfirm>
</template>
</pure-table>
</template>
</PureTableBar>
</div>
</template>
<script setup lang="ts">
defineOptions({
name: "{{ class_name }}Index"
});
import { ref } from "vue";
import { use[[ class_name ]] } from "./utils/hook";
import { useI18n } from "vue-i18n";
import { PureTableBar } from "@/components/RePureTableBar";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import Delete from "@iconify-icons/ep/delete";
import EditPen from "@iconify-icons/ep/edit-pen";
import Refresh from "@iconify-icons/ep/refresh";
import AddFill from "@iconify-icons/ri/add-circle-line";
const { t } = useI18n();
import { hasAuth } from "@/utils/auth";
/**
* 表格Ref
*/
const tableRef = ref();
const formRef = ref();
const {
form,
dataList,
loading,
pagination,
columns,
selectedNum,
onSearch,
openDialog,
resetForm,
handleDelete,
handleSizeChange,
handleCurrentChange,
handleSelectionChange,
onSelectionCancel,
onbatchDel
} = use[[ class_name ]](tableRef);
</script>
<style scoped lang="scss">
:deep(.el-dropdown-menu__item i) {
margin: 0;
}
:deep(.el-button:focus-visible) {
outline: none;
}
.main-content {
margin: 24px 24px 0 !important;
}
.search-form {
:deep(.el-form-item) {
margin-bottom: 12px;
}
}
</style>

110
utils/generate.py Normal file
View File

@@ -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<string, any>",
"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")
}