mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-22 05:02:57 +00:00
refactor: 大规模代码整理与功能优化
1. 重构后端API路由、CRUD与模块结构,整合日志管理,移除废弃demo代码 2. 优化前端组件类型定义、样式与路由配置,修复权限判断逻辑 3. 调整默认排序规则、滚动条样式与工具类函数,更新依赖与配置文件 4. 修复多处类型不匹配与默认值问题,完善表单与菜单验证逻辑
This commit is contained in:
@@ -3,11 +3,11 @@ from typing import Annotated
|
||||
from fastapi import APIRouter, Body, Depends, Path
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.common.response import ResponseSchema, StreamResponse, SuccessResponse
|
||||
from app.core.base_params import PaginationQueryParam
|
||||
from app.core.base_schema import AuthSchema, PageResultSchema
|
||||
from app.core.dependencies import AuthPermission
|
||||
from app.core.logger import log
|
||||
from app.core.logger import logger
|
||||
from app.core.router_class import OperationLogRoute
|
||||
from app.utils.common_util import bytes2file_response
|
||||
|
||||
@@ -21,13 +21,12 @@ from .schema import (
|
||||
)
|
||||
from .service import GenTableService
|
||||
|
||||
GenRouter = APIRouter(route_class=OperationLogRoute, prefix="/gencode", tags=["代码生成模块"])
|
||||
GenRouter = APIRouter(route_class=OperationLogRoute, prefix="/gencode", tags=["开发工具/代码生成"])
|
||||
|
||||
|
||||
@GenRouter.get(
|
||||
"/list",
|
||||
summary="查询代码生成业务表列表",
|
||||
description="查询代码生成业务表列表",
|
||||
response_model=ResponseSchema[list[GenTableOutSchema]],
|
||||
)
|
||||
async def gen_table_list_controller(
|
||||
@@ -56,15 +55,13 @@ async def gen_table_list_controller(
|
||||
search=search,
|
||||
order_by=order_by,
|
||||
)
|
||||
log.info("获取代码生成业务表列表成功")
|
||||
return SuccessResponse(data=result_dict, msg="获取代码生成业务表列表成功")
|
||||
|
||||
|
||||
@GenRouter.get(
|
||||
"/db/list",
|
||||
summary="查询数据库表列表",
|
||||
description="查询数据库表列表",
|
||||
response_model=ResponseSchema[list[GenDBTableSchema]],
|
||||
response_model=ResponseSchema[PageResultSchema[GenDBTableSchema]],
|
||||
)
|
||||
async def get_gen_db_table_list_controller(
|
||||
page: Annotated[PaginationQueryParam, Depends()],
|
||||
@@ -89,14 +86,12 @@ async def get_gen_db_table_list_controller(
|
||||
page_size=page.page_size,
|
||||
search=search,
|
||||
)
|
||||
log.info("获取数据库表列表成功")
|
||||
return SuccessResponse(data=result_dict, msg="获取数据库表列表成功")
|
||||
|
||||
|
||||
@GenRouter.post(
|
||||
"/import",
|
||||
summary="导入表结构",
|
||||
description="导入表结构",
|
||||
response_model=ResponseSchema[bool],
|
||||
)
|
||||
async def import_gen_table_controller(
|
||||
@@ -120,14 +115,12 @@ async def import_gen_table_controller(
|
||||
auth, table_names
|
||||
)
|
||||
result = await GenTableService.import_gen_table_service(auth, add_gen_table_list)
|
||||
log.info("导入表结构成功")
|
||||
return SuccessResponse(msg="导入表结构成功", data=result)
|
||||
|
||||
|
||||
@GenRouter.get(
|
||||
"/detail/{table_id}",
|
||||
summary="获取业务表详细信息",
|
||||
description="获取业务表详细信息",
|
||||
response_model=ResponseSchema[GenTableOutSchema],
|
||||
)
|
||||
async def gen_table_detail_controller(
|
||||
@@ -145,14 +138,12 @@ async def gen_table_detail_controller(
|
||||
- JSONResponse: 包含业务表详细信息的JSON响应
|
||||
"""
|
||||
gen_table_detail_result = await GenTableService.get_gen_table_detail_service(auth, table_id)
|
||||
log.info(f"获取table_id为{table_id}的信息成功")
|
||||
return SuccessResponse(data=gen_table_detail_result, msg="获取业务表详细信息成功")
|
||||
|
||||
|
||||
@GenRouter.post(
|
||||
"/create",
|
||||
summary="创建表结构",
|
||||
description="创建表结构",
|
||||
response_model=ResponseSchema[bool],
|
||||
)
|
||||
async def create_table_controller(
|
||||
@@ -173,14 +164,12 @@ async def create_table_controller(
|
||||
- JSONResponse: 包含创建结果的JSON响应
|
||||
"""
|
||||
result = await GenTableService.create_table_service(auth, body.sql)
|
||||
log.info("创建表结构成功")
|
||||
return SuccessResponse(msg="创建表结构成功", data=result)
|
||||
|
||||
|
||||
@GenRouter.put(
|
||||
"/update/{table_id}",
|
||||
summary="编辑业务表信息",
|
||||
description="编辑业务表信息",
|
||||
response_model=ResponseSchema[GenTableOutSchema],
|
||||
)
|
||||
async def update_gen_table_controller(
|
||||
@@ -203,14 +192,12 @@ async def update_gen_table_controller(
|
||||
- JSONResponse: 包含编辑结果的JSON响应
|
||||
"""
|
||||
result_dict = await GenTableService.update_gen_table_service(auth, data, table_id)
|
||||
log.info("编辑业务表信息成功")
|
||||
return SuccessResponse(data=result_dict, msg="编辑业务表信息成功")
|
||||
|
||||
|
||||
@GenRouter.delete(
|
||||
"/delete",
|
||||
summary="删除业务表信息",
|
||||
description="删除业务表信息",
|
||||
response_model=ResponseSchema[None],
|
||||
)
|
||||
async def delete_gen_table_controller(
|
||||
@@ -231,14 +218,12 @@ async def delete_gen_table_controller(
|
||||
- JSONResponse: 包含删除结果的JSON响应
|
||||
"""
|
||||
result = await GenTableService.delete_gen_table_service(auth, ids)
|
||||
log.info("删除业务表信息成功")
|
||||
return SuccessResponse(msg="删除业务表信息成功", data=result)
|
||||
|
||||
|
||||
@GenRouter.patch(
|
||||
"/batch/output",
|
||||
summary="批量生成代码",
|
||||
description="批量生成代码",
|
||||
)
|
||||
async def batch_gen_code_controller(
|
||||
table_names: Annotated[list[str], Body(description="表名列表")],
|
||||
@@ -254,19 +239,21 @@ async def batch_gen_code_controller(
|
||||
返回:
|
||||
- StreamResponse: 包含批量生成代码的ZIP文件流响应
|
||||
"""
|
||||
batch_gen_code_result = await GenTableService.batch_gen_code_service(auth, table_names)
|
||||
log.info(f"批量生成代码成功,表名列表:{table_names}")
|
||||
batch_gen_code_result, failed_tables = await GenTableService.batch_gen_code_service(auth, table_names)
|
||||
headers = {"Content-Disposition": "attachment; filename=code.zip"}
|
||||
if failed_tables:
|
||||
logger.warning(f"批量生成代码部分失败,跳过表: {failed_tables}")
|
||||
headers["X-Skipped-Tables"] = ",".join(failed_tables)
|
||||
return StreamResponse(
|
||||
data=bytes2file_response(batch_gen_code_result),
|
||||
media_type="application/zip",
|
||||
headers={"Content-Disposition": "attachment; filename=code.zip"},
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
|
||||
@GenRouter.post(
|
||||
"/output/{table_name}",
|
||||
summary="生成代码到指定路径",
|
||||
description="生成代码到指定路径",
|
||||
response_model=ResponseSchema[bool],
|
||||
)
|
||||
async def gen_code_local_controller(
|
||||
@@ -284,15 +271,13 @@ async def gen_code_local_controller(
|
||||
- JSONResponse: 包含生成结果的JSON响应
|
||||
"""
|
||||
result = await GenTableService.generate_code_service(auth, table_name)
|
||||
log.info(f"生成代码,表名:{table_name},到指定路径成功")
|
||||
return SuccessResponse(msg="生成代码到指定路径成功", data=result)
|
||||
|
||||
|
||||
@GenRouter.get(
|
||||
"/preview/{table_id}",
|
||||
summary="预览代码",
|
||||
description="预览代码",
|
||||
response_model=ResponseSchema[dict],
|
||||
response_model=ResponseSchema[GenTableOutSchema],
|
||||
)
|
||||
async def preview_code_controller(
|
||||
table_id: Annotated[int, Path(description="业务表ID")],
|
||||
@@ -309,14 +294,12 @@ async def preview_code_controller(
|
||||
- JSONResponse: 包含预览代码的JSON响应
|
||||
"""
|
||||
preview_code_result = await GenTableService.preview_code_service(auth, table_id)
|
||||
log.info(f"预览代码,表id:{table_id},成功")
|
||||
return SuccessResponse(data=preview_code_result, msg="预览代码成功")
|
||||
|
||||
|
||||
@GenRouter.post(
|
||||
"/sync_db/{table_name}",
|
||||
summary="同步数据库",
|
||||
description="同步数据库",
|
||||
response_model=ResponseSchema[None],
|
||||
)
|
||||
async def sync_db_controller(
|
||||
@@ -334,14 +317,12 @@ async def sync_db_controller(
|
||||
- JSONResponse: 包含同步数据库结果的JSON响应
|
||||
"""
|
||||
result = await GenTableService.sync_db_service(auth, table_name)
|
||||
log.info(f"同步数据库,表名:{table_name},成功")
|
||||
return SuccessResponse(msg="同步数据库成功", data=result)
|
||||
|
||||
|
||||
@GenRouter.get(
|
||||
"/sync_db/preview/{table_name}",
|
||||
summary="同步数据库差异预览",
|
||||
description="同步数据库前差异预览(主表 + 可选子表),不落库",
|
||||
response_model=ResponseSchema[GenSyncPreviewSchema],
|
||||
)
|
||||
async def sync_db_preview_controller(
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import asyncio
|
||||
from collections.abc import Sequence
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import Inspector, inspect, select, text
|
||||
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.config.setting import settings
|
||||
from app.core.base_crud import CRUDBase
|
||||
from app.core.logger import log
|
||||
from app.core.base_schema import AuthSchema
|
||||
from app.core.logger import logger
|
||||
|
||||
from .model import GenTableColumnModel, GenTableModel
|
||||
from .schema import (
|
||||
@@ -166,7 +167,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
)
|
||||
table_comment = comment or ""
|
||||
except Exception as e:
|
||||
log.warning(f"获取表 {table_name} 的注释失败: {e}")
|
||||
logger.warning(f"获取表 {table_name} 的注释失败: {e}")
|
||||
table_comment = ""
|
||||
|
||||
# 统一处理 search 为 None 的情况,避免重复判断
|
||||
@@ -329,21 +330,42 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
返回:
|
||||
- list[GenDBTableSchema]: 数据库表信息对象列表。
|
||||
"""
|
||||
# 处理空列表情况
|
||||
if not table_names:
|
||||
return []
|
||||
# 调用get_db_table_list获取所有表信息
|
||||
all_tables = await self.get_db_table_list()
|
||||
|
||||
# 过滤出指定名称的表
|
||||
table_names_set = set(table_names) # 转换为集合以提高查找效率
|
||||
filtered_tables = [
|
||||
GenDBTableSchema(**table)
|
||||
for table in all_tables
|
||||
if table["table_name"] in table_names_set
|
||||
]
|
||||
database_name = settings.DATABASE_NAME
|
||||
database_type = settings.DATABASE_TYPE
|
||||
|
||||
return filtered_tables
|
||||
from app.core.database import engine
|
||||
|
||||
inspector: Inspector = inspect(engine)
|
||||
all_table_names = set(inspector.get_table_names())
|
||||
|
||||
results = []
|
||||
for table_name in table_names:
|
||||
if table_name not in all_table_names:
|
||||
continue
|
||||
try:
|
||||
table_comment = inspector.get_table_comment(table_name)
|
||||
comment = (
|
||||
table_comment.get("text", "")
|
||||
if isinstance(table_comment, dict)
|
||||
else (table_comment or "")
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f"获取表 {table_name} 的注释失败: {e}")
|
||||
comment = ""
|
||||
|
||||
results.append(
|
||||
GenDBTableSchema(
|
||||
database_name=database_name,
|
||||
table_name=table_name,
|
||||
table_type=database_type,
|
||||
table_comment=comment or "",
|
||||
)
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
async def check_table_exists(self, table_name: str) -> bool:
|
||||
"""
|
||||
@@ -384,7 +406,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
)
|
||||
return comment or ""
|
||||
except Exception as e:
|
||||
log.warning(f"获取表 {table_name} 的注释失败: {e}")
|
||||
logger.warning(f"获取表 {table_name} 的注释失败: {e}")
|
||||
return ""
|
||||
|
||||
async def execute_sql(self, sql: str) -> bool:
|
||||
@@ -402,7 +424,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
|
||||
await self.auth.db.execute(text(sql))
|
||||
return True
|
||||
except Exception as e:
|
||||
log.error(f"执行SQL时发生错误: {e}")
|
||||
logger.error(f"执行SQL时发生错误: {e}")
|
||||
return False
|
||||
|
||||
|
||||
@@ -470,11 +492,8 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
|
||||
# 判断是否为自增列(基于数据库类型和列类型)
|
||||
is_increment = column.get("autoincrement", False) in (True, "auto")
|
||||
# 获取列长度(如果适用)
|
||||
column_length = None
|
||||
# 使用getattr安全地获取length属性,避免访问不存在时抛出AttributeError
|
||||
column_length = getattr(column["type"], "length", None)
|
||||
if column_length is not None:
|
||||
column_length = str(getattr(column["type"], "length", ""))
|
||||
col_len = getattr(column["type"], "length", None)
|
||||
column_length = str(col_len) if col_len is not None else ""
|
||||
|
||||
# 构造列信息字典
|
||||
column_info = {
|
||||
@@ -557,9 +576,11 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
|
||||
raise ValueError("数据表名称不能为空")
|
||||
|
||||
try:
|
||||
# 直接调用同步方法获取列信息
|
||||
columns_info = GenTableColumnCRUD._sync_get_table_columns(
|
||||
settings.DATABASE_TYPE, table_name
|
||||
# 在线程池中执行同步 inspect 操作,避免阻塞事件循环
|
||||
columns_info = await asyncio.to_thread(
|
||||
GenTableColumnCRUD._sync_get_table_columns,
|
||||
settings.DATABASE_TYPE,
|
||||
table_name,
|
||||
)
|
||||
|
||||
# 转换为GenTableColumnOutSchema对象列表
|
||||
@@ -567,7 +588,7 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
|
||||
|
||||
return columns_list
|
||||
except Exception as e:
|
||||
log.error(f"获取表{table_name}的字段列表时出错: {e!s}")
|
||||
logger.error(f"获取表{table_name}的字段列表时出错: {e!s}")
|
||||
# 确保即使出错也返回空列表而不是None
|
||||
raise
|
||||
|
||||
@@ -614,9 +635,7 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
|
||||
返回:
|
||||
- GenTableColumnModel | None: 业务表字段列表信息对象。
|
||||
"""
|
||||
# 将对象转换为字典,避免SQLAlchemy直接操作对象时出现的状态问题
|
||||
data_dict = data.model_dump(exclude_unset=True)
|
||||
return await self.update(id=id, data=data_dict)
|
||||
return await self.update(id=id, data=data)
|
||||
|
||||
async def delete_gen_table_column_by_table_id_crud(self, table_ids: list[int]) -> None:
|
||||
"""根据业务表ID批量删除业务表字段。
|
||||
|
||||
@@ -94,7 +94,7 @@ class GenTableSchema(BaseModel):
|
||||
default=None,
|
||||
description=(
|
||||
"功能子目录/路由段;导入时默认表名;同 module_name 下多表须不同。"
|
||||
"可含斜杠表示嵌套,参考 module_example:demo、demo/demo01、gen_demo02。"
|
||||
"可含斜杠表示嵌套,参考 module_example:demo、demo/subdir、gen_demo。"
|
||||
),
|
||||
)
|
||||
function_name: str | None = Field(default=None, description="生成功能名")
|
||||
@@ -223,7 +223,7 @@ class GenTableSchema(BaseModel):
|
||||
@classmethod
|
||||
def normalize_business_name(cls, v: str | None) -> str | None:
|
||||
"""
|
||||
业务名允许多段(如 demo/demo01);统一按 slug 规范。
|
||||
业务名允许多段(如 demo/subdir);统一按 slug 规范。
|
||||
|
||||
参数:
|
||||
- v (str | None): 原始业务名。
|
||||
|
||||
@@ -19,12 +19,13 @@ from sqlglot.expressions import (
|
||||
Update,
|
||||
)
|
||||
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.common.constant import GenConstant
|
||||
from app.common.enums import QueueEnum
|
||||
from app.config.path_conf import BASE_DIR
|
||||
from app.config.setting import settings
|
||||
from app.core.base_schema import AuthSchema
|
||||
from app.core.exceptions import CustomException
|
||||
from app.core.logger import log
|
||||
from app.core.logger import logger
|
||||
|
||||
from .crud import GenTableColumnCRUD, GenTableCRUD
|
||||
from .schema import (
|
||||
@@ -62,7 +63,7 @@ def handle_service_exception(func: Callable) -> Callable:
|
||||
return wrapper
|
||||
|
||||
|
||||
_MENU_TYPE_CATALOG = 1 # 与 sys_menu.type、前端 MenuTypeEnum.CATALOG 一致
|
||||
_MENU_TYPE_CATALOG = 1 # 与 platform_menu.type、前端 MenuTypeEnum.CATALOG 一致
|
||||
_MENU_TYPE_MENU = 2
|
||||
|
||||
|
||||
@@ -82,9 +83,9 @@ class GenTableService:
|
||||
pn = (package_name or "").strip()
|
||||
# 1) 选择上级目录:从上级菜单 route_path 第一段推断 module_xxx
|
||||
if parent_catalog_id is not None:
|
||||
from app.api.v1.module_system.menu.crud import MenuCRUD
|
||||
from app.api.v1.module_platform.menu.crud import MenuCRUD
|
||||
|
||||
m = await MenuCRUD(auth).get_by_id_crud(parent_catalog_id)
|
||||
m = await MenuCRUD(auth).get(id=parent_catalog_id)
|
||||
if not m:
|
||||
raise CustomException(msg="上级菜单不存在")
|
||||
route_path = (getattr(m, "route_path", None) or "").strip()
|
||||
@@ -110,9 +111,9 @@ class GenTableService:
|
||||
"""上级菜单仅允许目录:与前端树只展示目录一致,避免挂到菜单/按钮下。"""
|
||||
if parent_menu_id is None:
|
||||
return
|
||||
from app.api.v1.module_system.menu.crud import MenuCRUD
|
||||
from app.api.v1.module_platform.menu.crud import MenuCRUD
|
||||
|
||||
m = await MenuCRUD(auth).get_by_id_crud(parent_menu_id)
|
||||
m = await MenuCRUD(auth).get(id=parent_menu_id)
|
||||
if not m:
|
||||
raise CustomException(msg="上级菜单不存在")
|
||||
if m.type != _MENU_TYPE_CATALOG:
|
||||
@@ -161,7 +162,7 @@ class GenTableService:
|
||||
business_name: str,
|
||||
) -> int:
|
||||
"""创建或复用 type=1 模块目录;固定为「目录 → 菜单 → 按钮」中的第一层目录。"""
|
||||
from app.api.v1.module_system.menu.schema import MenuCreateSchema
|
||||
from app.api.v1.module_platform.menu.schema import MenuCreateSchema
|
||||
from app.utils.common_util import CamelCaseUtil
|
||||
|
||||
pn = (package_name or "").strip()
|
||||
@@ -176,10 +177,10 @@ class GenTableService:
|
||||
)
|
||||
else:
|
||||
existing = await menu_crud.get(
|
||||
name=dir_key, type=_MENU_TYPE_CATALOG, parent_id=("None", None)
|
||||
name=dir_key, type=_MENU_TYPE_CATALOG, parent_id=(QueueEnum.none.value, None)
|
||||
)
|
||||
if existing:
|
||||
log.info(
|
||||
logger.info(
|
||||
f"代码生成:复用模块目录菜单 id={existing.id} name={dir_key!r} parent={parent_catalog_id!r}"
|
||||
)
|
||||
return int(existing.id)
|
||||
@@ -212,7 +213,7 @@ class GenTableService:
|
||||
description="模块目录(代码生成)",
|
||||
)
|
||||
)
|
||||
log.info(
|
||||
logger.info(
|
||||
f"代码生成:新建模块目录菜单 id={created.id} name={dir_key!r} under_parent={parent_catalog_id!r}"
|
||||
)
|
||||
return int(created.id)
|
||||
@@ -241,7 +242,7 @@ class GenTableService:
|
||||
|
||||
@classmethod
|
||||
@handle_service_exception
|
||||
async def get_gen_table_detail_service(cls, auth: AuthSchema, table_id: int) -> dict:
|
||||
async def get_gen_table_detail_service(cls, auth: AuthSchema, table_id: int) -> GenTableOutSchema:
|
||||
"""获取详细信息。
|
||||
|
||||
参数:
|
||||
@@ -252,7 +253,7 @@ class GenTableService:
|
||||
- dict: 包含业务表详细信息的字典。
|
||||
"""
|
||||
gen_table = await cls.get_gen_table_by_id_service(auth, table_id)
|
||||
return gen_table.model_dump()
|
||||
return gen_table
|
||||
|
||||
@classmethod
|
||||
@handle_service_exception
|
||||
@@ -270,7 +271,7 @@ class GenTableService:
|
||||
- list[dict]: 包含业务表列表信息的字典列表。
|
||||
"""
|
||||
gen_table_list_result = await GenTableCRUD(auth=auth).get_gen_table_list(search)
|
||||
return [GenTableOutSchema.model_validate(obj).model_dump() for obj in gen_table_list_result]
|
||||
return [GenTableOutSchema.model_validate(obj) for obj in gen_table_list_result]
|
||||
|
||||
@classmethod
|
||||
@handle_service_exception
|
||||
@@ -504,7 +505,7 @@ class GenTableService:
|
||||
if not isinstance(sql_statement, (Create, Comment, Alter)):
|
||||
continue
|
||||
exc_sql = sql_statement.sql(dialect=settings.DATABASE_TYPE)
|
||||
log.info(f"执行SQL语句: {exc_sql}")
|
||||
logger.info(f"执行SQL语句: {exc_sql}")
|
||||
|
||||
# ALTER 仅允许添加外键约束,避免任意 ALTER 带来的破坏性
|
||||
if isinstance(sql_statement, Alter):
|
||||
@@ -524,6 +525,15 @@ class GenTableService:
|
||||
)
|
||||
if not await gen_table_crud.execute_sql(exc_sql):
|
||||
raise CustomException(msg=f"执行SQL语句 {exc_sql} 失败,请检查数据库")
|
||||
|
||||
# 建表成功后自动导入到代码生成模块
|
||||
if table_names:
|
||||
gen_table_list = await cls.get_gen_db_table_list_by_name_service(
|
||||
auth, table_names
|
||||
)
|
||||
if gen_table_list:
|
||||
await cls.import_gen_table_service(auth, gen_table_list)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
@@ -533,7 +543,7 @@ class GenTableService:
|
||||
@handle_service_exception
|
||||
async def update_gen_table_service(
|
||||
cls, auth: AuthSchema, data: GenTableSchema, table_id: int
|
||||
) -> dict[str, Any]:
|
||||
) -> GenTableOutSchema:
|
||||
"""编辑业务表信息。
|
||||
|
||||
参数:
|
||||
@@ -555,21 +565,54 @@ class GenTableService:
|
||||
if not result:
|
||||
raise CustomException(msg="更新业务表信息失败")
|
||||
|
||||
# 处理data.columns为None的情况
|
||||
if data.columns:
|
||||
if data.columns is not None:
|
||||
db_columns = await GenTableColumnCRUD(auth).list_gen_table_column_crud(
|
||||
search={"table_id": table_id}
|
||||
)
|
||||
db_column_map = {c.column_name: c for c in db_columns if c.column_name}
|
||||
submitted_names = {
|
||||
c.column_name
|
||||
for c in data.columns
|
||||
if hasattr(c, "column_name") and c.column_name
|
||||
}
|
||||
|
||||
for gen_table_column in data.columns:
|
||||
# 确保column有id字段
|
||||
if hasattr(gen_table_column, "id") and gen_table_column.id:
|
||||
column_schema = GenTableColumnSchema(**gen_table_column.model_dump())
|
||||
await GenTableColumnCRUD(auth).update_gen_table_column_crud(
|
||||
gen_table_column.id, column_schema
|
||||
col_id = getattr(gen_table_column, "id", None)
|
||||
col_name = getattr(gen_table_column, "column_name", None)
|
||||
if col_id and col_name and col_name in db_column_map:
|
||||
# 只更新前端实际修改的字段(利用 Pydantic model_fields_set)
|
||||
update_data = gen_table_column.model_dump(
|
||||
exclude_unset=True, exclude={"id", "super_column"}
|
||||
)
|
||||
if update_data:
|
||||
await GenTableColumnCRUD(auth).update(
|
||||
id=col_id, data=update_data
|
||||
)
|
||||
else:
|
||||
# 新增列:前端新增但库中无对应记录
|
||||
column_schema = GenTableColumnSchema(
|
||||
table_id=table_id,
|
||||
**gen_table_column.model_dump(
|
||||
exclude={"id", "super_column"}
|
||||
),
|
||||
)
|
||||
GenUtils.init_column_field(column_schema, gen_table_info)
|
||||
await GenTableColumnCRUD(auth).create_gen_table_column_crud(
|
||||
column_schema
|
||||
)
|
||||
|
||||
# 删除前端已移除的列
|
||||
for db_name, db_col in db_column_map.items():
|
||||
if db_name not in submitted_names:
|
||||
db_id = getattr(db_col, "id", None)
|
||||
if db_id:
|
||||
await GenTableColumnCRUD(auth).delete(ids=[db_id])
|
||||
# 重新获取带有预加载关系的对象,避免懒加载导致的MissingGreenlet错误
|
||||
updated_gen_table = await GenTableCRUD(auth).get_gen_table_by_id(table_id)
|
||||
out = GenTableOutSchema.model_validate(updated_gen_table)
|
||||
await cls.set_pk_column(out)
|
||||
await cls.hydrate_sub_table(auth, out)
|
||||
return out.model_dump()
|
||||
return out
|
||||
except CustomException:
|
||||
raise
|
||||
except Exception as e:
|
||||
@@ -642,7 +685,7 @@ class GenTableService:
|
||||
table_out = GenTableOutSchema.model_validate(gen_table)
|
||||
result.append(table_out)
|
||||
except Exception as e:
|
||||
log.error(f"转换业务表时出错: {e!s}")
|
||||
logger.error(f"转换业务表时出错: {e!s}")
|
||||
continue
|
||||
return result
|
||||
|
||||
@@ -687,7 +730,7 @@ class GenTableService:
|
||||
out_key = Jinja2TemplateUtil.get_file_name(template, gen_table)
|
||||
preview_code_result[out_key] = render_content
|
||||
except Exception as e:
|
||||
log.error(f"渲染模板 {template} 时出错: {e!s}")
|
||||
logger.error(f"渲染模板 {template} 时出错: {e!s}")
|
||||
out_key = Jinja2TemplateUtil.get_file_name(template, gen_table)
|
||||
preview_code_result[out_key] = f"渲染错误: {e!s}"
|
||||
if gen_table.sub and gen_table.sub_table:
|
||||
@@ -700,7 +743,7 @@ class GenTableService:
|
||||
out_key = Jinja2TemplateUtil.get_file_name(template, sub_table)
|
||||
preview_code_result[out_key] = render_content
|
||||
except Exception as e:
|
||||
log.error(f"渲染子表模板 {template} 时出错: {e!s}")
|
||||
logger.error(f"渲染子表模板 {template} 时出错: {e!s}")
|
||||
out_key = Jinja2TemplateUtil.get_file_name(template, sub_table)
|
||||
preview_code_result[out_key] = f"渲染错误: {e!s}"
|
||||
return preview_code_result
|
||||
@@ -733,8 +776,8 @@ class GenTableService:
|
||||
render_info = await cls.__get_gen_render_info(auth, table_name)
|
||||
gen_table_schema: GenTableOutSchema = render_info[3]
|
||||
|
||||
from app.api.v1.module_system.menu.crud import MenuCRUD
|
||||
from app.api.v1.module_system.menu.schema import MenuCreateSchema
|
||||
from app.api.v1.module_platform.menu.crud import MenuCRUD
|
||||
from app.api.v1.module_platform.menu.schema import MenuCreateSchema
|
||||
from app.utils.common_util import CamelCaseUtil
|
||||
|
||||
# 按“上级目录”规则矫正最终包名(分系统根)
|
||||
@@ -748,12 +791,54 @@ class GenTableService:
|
||||
if not mn:
|
||||
raise CustomException(msg="模块名不能为空")
|
||||
permission_prefix = ":".join([s for s in [pn, mn] if s])
|
||||
# 创建菜单 CRUD 实例
|
||||
menu_crud = MenuCRUD(auth)
|
||||
if not gen_table_schema.function_name:
|
||||
raise CustomException(msg="功能名称不能为空")
|
||||
if not gen_table_schema.package_name:
|
||||
raise CustomException(msg="包名不能为空")
|
||||
|
||||
# 1. 先写代码文件(风险最高,放最前,失败不产生菜单孤儿数据)
|
||||
async def _write_templates(
|
||||
templates: list[str], ctx: dict[str, Any], table_schema: GenTableOutSchema
|
||||
) -> None:
|
||||
for template in templates:
|
||||
try:
|
||||
render_content = await env.get_template(template).render_async(**ctx)
|
||||
file_name = Jinja2TemplateUtil.get_file_name(template, table_schema)
|
||||
full_path = BASE_DIR.parent.joinpath(file_name)
|
||||
gen_path = str(full_path)
|
||||
if not gen_path:
|
||||
raise CustomException(msg="【代码生成】生成路径为空")
|
||||
os.makedirs(os.path.dirname(gen_path), exist_ok=True)
|
||||
await anyio.Path(gen_path).write_text(render_content, encoding="utf-8")
|
||||
# Python 插件目录需保证包层级可导入:为分系统/模块目录补齐 __init__.py
|
||||
pn_inner = (table_schema.package_name or "").strip()
|
||||
mn_inner = (table_schema.module_name or "").strip()
|
||||
if pn_inner and mn_inner:
|
||||
plugin_base = BASE_DIR.parent.joinpath(f"backend/app/plugin/{pn_inner}")
|
||||
module_base = plugin_base.joinpath(mn_inner)
|
||||
for d in (plugin_base, module_base):
|
||||
init_path = d.joinpath("__init__.py")
|
||||
if not init_path.exists():
|
||||
os.makedirs(str(d), exist_ok=True)
|
||||
await anyio.Path(str(init_path)).write_text(
|
||||
"# -*- coding: utf-8 -*-", encoding="utf-8"
|
||||
)
|
||||
except Exception as e:
|
||||
raise CustomException(
|
||||
msg=f"渲染模板失败,表名:{table_schema.table_name},详细错误信息:{e!s}"
|
||||
)
|
||||
|
||||
await _write_templates(render_info[0], render_info[2], gen_table_schema)
|
||||
if gen_table_schema.sub and gen_table_schema.sub_table:
|
||||
gen_table_schema.sub_table.package_name = gen_table_schema.package_name
|
||||
sub_ctx = Jinja2TemplateUtil.prepare_sub_render_context(
|
||||
gen_table_schema, gen_table_schema.sub_table
|
||||
)
|
||||
sub_templates = Jinja2TemplateUtil.get_sub_table_template_list()
|
||||
await _write_templates(sub_templates, sub_ctx, gen_table_schema.sub_table)
|
||||
|
||||
# 2. 代码成功写入后,再创建菜单(避免失败时产生孤儿菜单数据)
|
||||
menu_crud = MenuCRUD(auth)
|
||||
await cls._assert_parent_menu_is_catalog(auth, gen_table_schema.parent_menu_id)
|
||||
# 1. 目录 + 菜单 + 按钮:先取/建模块目录(名称规则见 _catalog_menu_dir_key)
|
||||
dir_menu_id = await cls._get_or_create_package_directory_menu(
|
||||
@@ -859,7 +944,14 @@ class GenTableService:
|
||||
},
|
||||
]
|
||||
for button in buttons:
|
||||
# 检查按钮权限是否已存在
|
||||
existing_btn = await menu_crud.get(
|
||||
permission=button["permission"],
|
||||
type=3,
|
||||
parent_id=parent_menu.id,
|
||||
)
|
||||
if existing_btn:
|
||||
logger.info(f"按钮权限已存在,跳过创建: {button['permission']}")
|
||||
continue
|
||||
await menu_crud.create(
|
||||
MenuCreateSchema(
|
||||
name=button["name"],
|
||||
@@ -882,58 +974,14 @@ class GenTableService:
|
||||
description=f"{gen_table_schema.function_name}功能按钮",
|
||||
)
|
||||
)
|
||||
log.info(f"成功创建按钮权限: {button['name']}")
|
||||
log.info(f"成功创建{gen_table_schema.function_name}菜单及按钮权限")
|
||||
|
||||
# 2. 菜单创建成功后,再生成页面代码(主表 + 可选子表)
|
||||
async def _write_templates(
|
||||
templates: list[str], ctx: dict[str, Any], table_schema: GenTableOutSchema
|
||||
) -> None:
|
||||
for template in templates:
|
||||
try:
|
||||
render_content = await env.get_template(template).render_async(**ctx)
|
||||
file_name = Jinja2TemplateUtil.get_file_name(template, table_schema)
|
||||
full_path = BASE_DIR.parent.joinpath(file_name)
|
||||
gen_path = str(full_path)
|
||||
if not gen_path:
|
||||
raise CustomException(msg="【代码生成】生成路径为空")
|
||||
os.makedirs(os.path.dirname(gen_path), exist_ok=True)
|
||||
await anyio.Path(gen_path).write_text(render_content, encoding="utf-8")
|
||||
# Python 插件目录需保证包层级可导入:为分系统/模块目录补齐 __init__.py
|
||||
# 生成规则固定为 backend/app/plugin/{module_xxx}/{module_name}/...
|
||||
pn = (table_schema.package_name or "").strip()
|
||||
mn = (table_schema.module_name or "").strip()
|
||||
if pn and mn:
|
||||
plugin_base = BASE_DIR.parent.joinpath(f"backend/app/plugin/{pn}")
|
||||
module_base = plugin_base.joinpath(mn)
|
||||
for d in (plugin_base, module_base):
|
||||
init_path = d.joinpath("__init__.py")
|
||||
if not init_path.exists():
|
||||
os.makedirs(str(d), exist_ok=True)
|
||||
await anyio.Path(str(init_path)).write_text(
|
||||
"# -*- coding: utf-8 -*-", encoding="utf-8"
|
||||
)
|
||||
except Exception as e:
|
||||
raise CustomException(
|
||||
msg=f"渲染模板失败,表名:{table_schema.table_name},详细错误信息:{e!s}"
|
||||
)
|
||||
|
||||
await _write_templates(render_info[0], render_info[2], gen_table_schema)
|
||||
if gen_table_schema.sub and gen_table_schema.sub_table:
|
||||
# 子表与主表同分系统,使用自己的模块名,实现同级目录
|
||||
gen_table_schema.sub_table.package_name = gen_table_schema.package_name
|
||||
# 确保子表使用自己的模块名,与主表同级目录
|
||||
sub_ctx = Jinja2TemplateUtil.prepare_sub_render_context(
|
||||
gen_table_schema, gen_table_schema.sub_table
|
||||
)
|
||||
sub_templates = Jinja2TemplateUtil.get_sub_table_template_list()
|
||||
await _write_templates(sub_templates, sub_ctx, gen_table_schema.sub_table)
|
||||
logger.info(f"成功创建按钮权限: {button['name']}")
|
||||
logger.info(f"成功创建{gen_table_schema.function_name}菜单及按钮权限")
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
@handle_service_exception
|
||||
async def batch_gen_code_service(cls, auth: AuthSchema, table_names: list[str]) -> bytes:
|
||||
async def batch_gen_code_service(cls, auth: AuthSchema, table_names: list[str]) -> tuple[bytes, list[str]]:
|
||||
"""
|
||||
批量生成代码并打包为ZIP。
|
||||
- 备注:内存生成并压缩,兼容多模板类型;供下载使用。
|
||||
@@ -950,6 +998,7 @@ class GenTableService:
|
||||
raise CustomException(msg="表名列表不能为空")
|
||||
zip_buffer = io.BytesIO()
|
||||
file_count = 0
|
||||
failed_tables: list[str] = []
|
||||
with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
|
||||
for table_name in valid_names:
|
||||
try:
|
||||
@@ -978,7 +1027,8 @@ class GenTableService:
|
||||
zip_file.writestr(out_path, render_content)
|
||||
file_count += 1
|
||||
except Exception as e:
|
||||
log.error(f"批量生成代码时处理表 {table_name} 出错: {e!s}")
|
||||
logger.error(f"批量生成代码时处理表 {table_name} 出错: {e!s}")
|
||||
failed_tables.append(table_name)
|
||||
# 继续处理其他表,不中断整个过程
|
||||
continue
|
||||
zip_data = zip_buffer.getvalue()
|
||||
@@ -987,7 +1037,7 @@ class GenTableService:
|
||||
raise CustomException(
|
||||
msg="未能生成任何代码文件:请检查所选表是否存在于代码生成配置中,或主子表、字段配置是否正确"
|
||||
)
|
||||
return zip_data
|
||||
return zip_data, failed_tables
|
||||
|
||||
@classmethod
|
||||
@handle_service_exception
|
||||
@@ -1175,7 +1225,7 @@ class GenTableService:
|
||||
sub_name_raw
|
||||
)
|
||||
except Exception as e:
|
||||
log.warning(f"获取子表 {sub_name_raw} 字段失败: {e!s}")
|
||||
logger.warning(f"获取子表 {sub_name_raw} 字段失败: {e!s}")
|
||||
gen_table.sub = False
|
||||
gen_table.sub_table = None
|
||||
gen_table.master_sub_hint = f"无法读取子表结构:{e!s}"
|
||||
@@ -1280,7 +1330,7 @@ class GenTableService:
|
||||
|
||||
@classmethod
|
||||
@handle_service_exception
|
||||
async def sync_db_preview_service(cls, auth: AuthSchema, table_name: str) -> dict[str, Any]:
|
||||
async def sync_db_preview_service(cls, auth: AuthSchema, table_name: str) -> GenSyncPreviewSchema:
|
||||
"""
|
||||
同步数据库前差异预览(主表 + 可选子表)。
|
||||
|
||||
@@ -1341,7 +1391,7 @@ class GenTableService:
|
||||
unchanged=s_unchanged,
|
||||
)
|
||||
|
||||
return preview.model_dump()
|
||||
return preview
|
||||
|
||||
@classmethod
|
||||
def _assert_master_sub_config_valid(cls, gen_table: GenTableOutSchema) -> None:
|
||||
@@ -1437,7 +1487,7 @@ class GenTableColumnService:
|
||||
"table_id": table_id
|
||||
})
|
||||
result = [
|
||||
GenTableColumnOutSchema.model_validate(gen_table_column).model_dump()
|
||||
GenTableColumnOutSchema.model_validate(gen_table_column)
|
||||
for gen_table_column in gen_table_column_list_result
|
||||
]
|
||||
return result
|
||||
|
||||
@@ -1,239 +1,243 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import urllib.parse
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, UploadFile, Body, Path, Query
|
||||
from fastapi.responses import StreamingResponse, JSONResponse
|
||||
from fastapi import APIRouter, Body, Depends, Path, UploadFile
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
|
||||
from app.common.response import SuccessResponse, StreamResponse
|
||||
from app.core.dependencies import AuthPermission
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.common.response import ResponseSchema, StreamResponse, SuccessResponse
|
||||
from app.core.base_params import PaginationQueryParam
|
||||
from app.core.base_schema import AuthSchema, BatchSetAvailable, PageResultSchema
|
||||
from app.core.dependencies import AuthPermission
|
||||
from app.core.router_class import OperationLogRoute
|
||||
from app.utils.common_util import bytes2file_response
|
||||
from app.core.logger import log
|
||||
from app.core.base_schema import BatchSetAvailable
|
||||
|
||||
from .schema import {{ class_name }}CreateSchema, {{ class_name }}OutSchema, {{ class_name }}QueryParam, {{ class_name }}UpdateSchema
|
||||
from .service import {{ class_name }}Service
|
||||
from .schema import {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema, {{ class_name }}QueryParam
|
||||
|
||||
# 动态路由容器前缀由 module_xxx 决定(discover: module_xxx -> /xxx)
|
||||
# 对齐 module_example/demo:业务路由前缀固定为 /{module_name}
|
||||
{{ class_name }}Router = APIRouter(prefix='/{{ module_name }}', tags=["{{ function_name }}模块"])
|
||||
{{ class_name }}Router = APIRouter(route_class=OperationLogRoute, prefix="/{{ module_name }}", tags=["{{ function_name }}模块"])
|
||||
|
||||
|
||||
@{{ class_name }}Router.get(
|
||||
"/detail/{id}",
|
||||
summary="获取{{ function_name }}详情",
|
||||
description="获取{{ function_name }}详情"
|
||||
response_model=ResponseSchema[{{ class_name }}OutSchema],
|
||||
)
|
||||
async def get_{{ business_name_slug }}_detail_controller(
|
||||
id: int = Path(..., description="ID"),
|
||||
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:query"]))
|
||||
async def get_obj_detail_controller(
|
||||
id: Annotated[int, Path(description="{{ function_name }}ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:detail"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
获取{{ function_name }}详情接口
|
||||
|
||||
获取{{ function_name }}详情
|
||||
|
||||
参数:
|
||||
- id: int - 数据ID
|
||||
- auth: AuthSchema - 认证信息
|
||||
|
||||
- id (int): {{ function_name }}ID
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- JSONResponse - 包含{{ function_name }}详情的JSON响应
|
||||
- JSONResponse: 包含{{ function_name }}详情的JSON响应
|
||||
"""
|
||||
result_dict = await {{ class_name }}Service.detail_{{ business_name_slug }}_service(auth=auth, id=id)
|
||||
log.info(f"获取{{ function_name }}详情成功 {id}")
|
||||
result_dict = await {{ class_name }}Service.detail_service(id=id, auth=auth)
|
||||
return SuccessResponse(data=result_dict, msg="获取{{ function_name }}详情成功")
|
||||
|
||||
|
||||
@{{ class_name }}Router.get(
|
||||
"/list",
|
||||
summary="查询{{ function_name }}列表",
|
||||
description="查询{{ function_name }}列表"
|
||||
summary="分页查询{{ function_name }}",
|
||||
response_model=ResponseSchema[PageResultSchema[{{ class_name }}OutSchema]],
|
||||
)
|
||||
async def get_{{ business_name_slug }}_list_controller(
|
||||
page: PaginationQueryParam = Depends(),
|
||||
search: {{ class_name }}QueryParam = Depends(),
|
||||
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:query"]))
|
||||
async def get_obj_list_controller(
|
||||
page: Annotated[PaginationQueryParam, Depends()],
|
||||
search: Annotated[{{ class_name }}QueryParam, Depends()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:query"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
查询{{ function_name }}列表接口(数据库分页)
|
||||
|
||||
查询{{ function_name }}列表
|
||||
|
||||
参数:
|
||||
- page: PaginationQueryParam - 分页参数
|
||||
- search: {{ class_name }}QueryParam - 查询参数
|
||||
- auth: AuthSchema - 认证信息
|
||||
|
||||
- page (PaginationQueryParam): 分页查询参数
|
||||
- search ({{ class_name }}QueryParam): 查询参数
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- JSONResponse - 包含{{ function_name }}列表的JSON响应
|
||||
- JSONResponse: 包含{{ function_name }}列表分页信息的JSON响应
|
||||
"""
|
||||
result_dict = await {{ class_name }}Service.page_{{ business_name_slug }}_service(
|
||||
result_dict = await {{ class_name }}Service.page_service(
|
||||
auth=auth,
|
||||
page_no=page.page_no if page.page_no is not None else 1,
|
||||
page_size=page.page_size if page.page_size is not None else 10,
|
||||
page_no=page.page_no,
|
||||
page_size=page.page_size,
|
||||
search=search,
|
||||
order_by=page.order_by
|
||||
order_by=page.order_by,
|
||||
)
|
||||
log.info("查询{{ function_name }}列表成功")
|
||||
return SuccessResponse(data=result_dict, msg="查询{{ function_name }}列表成功")
|
||||
|
||||
|
||||
@{{ class_name }}Router.post(
|
||||
"/create",
|
||||
summary="创建{{ function_name }}",
|
||||
description="创建{{ function_name }}"
|
||||
response_model=ResponseSchema[{{ class_name }}OutSchema],
|
||||
)
|
||||
async def create_{{ business_name_slug }}_controller(
|
||||
async def create_obj_controller(
|
||||
data: {{ class_name }}CreateSchema,
|
||||
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:create"]))
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:create"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
创建{{ function_name }}接口
|
||||
|
||||
创建{{ function_name }}
|
||||
|
||||
参数:
|
||||
- data: {{ class_name }}CreateSchema - 创建数据
|
||||
- auth: AuthSchema - 认证信息
|
||||
|
||||
- data ({{ class_name }}CreateSchema): {{ function_name }}创建模型
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- JSONResponse - 包含创建{{ function_name }}结果的JSON响应
|
||||
- JSONResponse: 包含创建{{ function_name }}详情的JSON响应
|
||||
"""
|
||||
result_dict = await {{ class_name }}Service.create_{{ business_name_slug }}_service(auth=auth, data=data)
|
||||
log.info("创建{{ function_name }}成功")
|
||||
result_dict = await {{ class_name }}Service.create_service(auth=auth, data=data)
|
||||
return SuccessResponse(data=result_dict, msg="创建{{ function_name }}成功")
|
||||
|
||||
|
||||
@{{ class_name }}Router.put(
|
||||
"/update/{id}",
|
||||
summary="修改{{ function_name }}",
|
||||
description="修改{{ function_name }}"
|
||||
response_model=ResponseSchema[{{ class_name }}OutSchema],
|
||||
)
|
||||
async def update_{{ business_name_slug }}_controller(
|
||||
async def update_obj_controller(
|
||||
data: {{ class_name }}UpdateSchema,
|
||||
id: int = Path(..., description="ID"),
|
||||
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:update"]))
|
||||
id: Annotated[int, Path(description="{{ function_name }}ID")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:update"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
修改{{ function_name }}接口
|
||||
|
||||
修改{{ function_name }}
|
||||
|
||||
参数:
|
||||
- id: int - 数据ID
|
||||
- data: {{ class_name }}UpdateSchema - 更新数据
|
||||
- auth: AuthSchema - 认证信息
|
||||
|
||||
- data ({{ class_name }}UpdateSchema): {{ function_name }}更新模型
|
||||
- id (int): {{ function_name }}ID
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- JSONResponse - 包含修改{{ function_name }}结果的JSON响应
|
||||
- JSONResponse: 包含修改{{ function_name }}详情的JSON响应
|
||||
"""
|
||||
result_dict = await {{ class_name }}Service.update_{{ business_name_slug }}_service(auth=auth, id=id, data=data)
|
||||
log.info("修改{{ function_name }}成功")
|
||||
result_dict = await {{ class_name }}Service.update_service(auth=auth, id=id, data=data)
|
||||
return SuccessResponse(data=result_dict, msg="修改{{ function_name }}成功")
|
||||
|
||||
|
||||
@{{ class_name }}Router.delete(
|
||||
"/delete",
|
||||
summary="删除{{ function_name }}",
|
||||
description="删除{{ function_name }}"
|
||||
response_model=ResponseSchema[None],
|
||||
)
|
||||
async def delete_{{ business_name_slug }}_controller(
|
||||
ids: list[int] = Body(..., description="ID列表"),
|
||||
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:delete"]))
|
||||
async def delete_obj_controller(
|
||||
ids: Annotated[list[int], Body(description="ID列表")],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:delete"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
删除{{ function_name }}接口
|
||||
|
||||
删除{{ function_name }}
|
||||
|
||||
参数:
|
||||
- ids: list[int] - 数据ID列表
|
||||
- auth: AuthSchema - 认证信息
|
||||
|
||||
- ids (list[int]): {{ function_name }}ID列表
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- JSONResponse - 包含删除{{ function_name }}结果的JSON响应
|
||||
- JSONResponse: 包含删除{{ function_name }}详情的JSON响应
|
||||
"""
|
||||
await {{ class_name }}Service.delete_{{ business_name_slug }}_service(auth=auth, ids=ids)
|
||||
log.info(f"删除{{ function_name }}成功: {ids}")
|
||||
await {{ class_name }}Service.delete_service(auth=auth, ids=ids)
|
||||
return SuccessResponse(msg="删除{{ function_name }}成功")
|
||||
|
||||
|
||||
@{{ class_name }}Router.patch(
|
||||
"/status/batch",
|
||||
"/available/setting",
|
||||
summary="批量修改{{ function_name }}状态",
|
||||
description="批量修改{{ function_name }}状态"
|
||||
response_model=ResponseSchema[None],
|
||||
)
|
||||
async def batch_set_available_{{ business_name_slug }}_controller(
|
||||
async def batch_set_available_obj_controller(
|
||||
data: BatchSetAvailable,
|
||||
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:patch"]))
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:patch"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
批量修改{{ function_name }}状态接口
|
||||
|
||||
批量修改{{ function_name }}状态
|
||||
|
||||
参数:
|
||||
- data: BatchSetAvailable - 批量修改状态数据
|
||||
- auth: AuthSchema - 认证信息
|
||||
|
||||
- data (BatchSetAvailable): 批量修改{{ function_name }}状态模型
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- JSONResponse - 包含批量修改{{ function_name }}状态结果的JSON响应
|
||||
- JSONResponse: 包含批量修改{{ function_name }}状态详情的JSON响应
|
||||
"""
|
||||
await {{ class_name }}Service.set_available_{{ business_name_slug }}_service(auth=auth, data=data)
|
||||
log.info(f"批量修改{{ function_name }}状态成功: {data.ids}")
|
||||
await {{ class_name }}Service.set_available_service(auth=auth, data=data)
|
||||
return SuccessResponse(msg="批量修改{{ function_name }}状态成功")
|
||||
|
||||
|
||||
@{{ class_name }}Router.post(
|
||||
'/export',
|
||||
"/export",
|
||||
summary="导出{{ function_name }}",
|
||||
description="导出{{ function_name }}"
|
||||
)
|
||||
async def export_{{ business_name_slug }}_list_controller(
|
||||
search: {{ class_name }}QueryParam = Depends(),
|
||||
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:export"]))
|
||||
async def export_obj_list_controller(
|
||||
search: Annotated[{{ class_name }}QueryParam, Depends()],
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:export"]))],
|
||||
) -> StreamingResponse:
|
||||
"""
|
||||
导出{{ function_name }}接口
|
||||
|
||||
导出{{ function_name }}
|
||||
|
||||
参数:
|
||||
- search: {{ class_name }}QueryParam - 查询参数
|
||||
- auth: AuthSchema - 认证信息
|
||||
|
||||
- search ({{ class_name }}QueryParam): 查询参数
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- StreamingResponse - 包含导出{{ function_name }}数据的流式响应
|
||||
- StreamingResponse: 包含{{ function_name }}列表的Excel文件流响应
|
||||
"""
|
||||
result_dict_list = await {{ class_name }}Service.list_{{ business_name_slug }}_service(search=search, auth=auth)
|
||||
export_result = await {{ class_name }}Service.batch_export_{{ business_name_slug }}_service(obj_list=result_dict_list)
|
||||
log.info('导出{{ function_name }}成功')
|
||||
result_dict_list = await {{ class_name }}Service.list_service(search=search, auth=auth)
|
||||
export_result = await {{ class_name }}Service.batch_export_service(obj_list=result_dict_list)
|
||||
|
||||
return StreamResponse(
|
||||
data=bytes2file_response(export_result),
|
||||
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
headers={'Content-Disposition': 'attachment; filename={{ table_name }}.xlsx'}
|
||||
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
headers={"Content-Disposition": "attachment; filename={{ table_name }}.xlsx"},
|
||||
)
|
||||
|
||||
@{{ class_name }}Router.post(
|
||||
'/import',
|
||||
summary="导入{{ function_name }}",
|
||||
description="导入{{ function_name }}"
|
||||
)
|
||||
async def import_{{ business_name_slug }}_list_controller(
|
||||
file: UploadFile,
|
||||
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:import"]))
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
导入{{ function_name }}接口
|
||||
|
||||
参数:
|
||||
- file: UploadFile - 上传的Excel文件
|
||||
- auth: AuthSchema - 认证信息
|
||||
|
||||
返回:
|
||||
- JSONResponse - 包含导入{{ function_name }}结果的JSON响应
|
||||
"""
|
||||
batch_import_result = await {{ class_name }}Service.batch_import_{{ business_name_slug }}_service(file=file, auth=auth, update_support=True)
|
||||
log.info("导入{{ function_name }}成功")
|
||||
return SuccessResponse(data=batch_import_result, msg="导入{{ function_name }}成功")
|
||||
|
||||
@{{ class_name }}Router.post(
|
||||
'/download/template',
|
||||
summary="获取{{ function_name }}导入模板",
|
||||
description="获取{{ function_name }}导入模板",
|
||||
dependencies=[Depends(AuthPermission(["{{ permission_prefix }}:download"]))]
|
||||
"/import",
|
||||
summary="导入{{ function_name }}",
|
||||
response_model=ResponseSchema[str],
|
||||
)
|
||||
async def export_{{ business_name_slug }}_template_controller() -> StreamingResponse:
|
||||
async def import_obj_list_controller(
|
||||
file: UploadFile,
|
||||
auth: Annotated[AuthSchema, Depends(AuthPermission(["{{ permission_prefix }}:import"]))],
|
||||
) -> JSONResponse:
|
||||
"""
|
||||
获取{{ function_name }}导入模板接口
|
||||
|
||||
导入{{ function_name }}
|
||||
|
||||
参数:
|
||||
- file (UploadFile): 导入的Excel文件
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
|
||||
返回:
|
||||
- StreamingResponse - 包含{{ function_name }}导入模板的流式响应
|
||||
- JSONResponse: 包含导入{{ function_name }}详情的JSON响应
|
||||
"""
|
||||
import_template_result = await {{ class_name }}Service.import_template_download_{{ business_name_slug }}_service()
|
||||
log.info('获取{{ function_name }}导入模板成功')
|
||||
batch_import_result = await {{ class_name }}Service.batch_import_service(
|
||||
file=file, auth=auth, update_support=True
|
||||
)
|
||||
return SuccessResponse(data=batch_import_result, msg="导入{{ function_name }}成功")
|
||||
|
||||
|
||||
@{{ class_name }}Router.post(
|
||||
"/download/template",
|
||||
summary="获取{{ function_name }}导入模板",
|
||||
dependencies=[Depends(AuthPermission(["{{ permission_prefix }}:download"]))],
|
||||
)
|
||||
async def export_obj_template_controller() -> StreamingResponse:
|
||||
"""
|
||||
获取{{ function_name }}导入模板
|
||||
|
||||
返回:
|
||||
- StreamingResponse: 包含{{ function_name }}导入模板的Excel文件流响应
|
||||
"""
|
||||
import_template_result = await {{ class_name }}Service.import_template_download_service()
|
||||
|
||||
return StreamResponse(
|
||||
data=bytes2file_response(import_template_result),
|
||||
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
headers={'Content-Disposition': 'attachment; filename={{ table_name }}_template.xlsx'}
|
||||
media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
headers={
|
||||
"Content-Disposition": f"attachment; filename={urllib.parse.quote('{{ function_name }}导入模板.xlsx')}",
|
||||
"Access-Control-Expose-Headers": "Content-Disposition",
|
||||
},
|
||||
)
|
||||
@@ -1,11 +1,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from typing import Sequence
|
||||
|
||||
from app.core.base_crud import CRUDBase
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from .model import {{ class_name }}Model
|
||||
from .schema import {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema, {{ class_name }}OutSchema
|
||||
from .schema import {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema
|
||||
|
||||
|
||||
class {{ class_name }}CRUD(CRUDBase[{{ class_name }}Model, {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema]):
|
||||
@@ -14,110 +12,8 @@ class {{ class_name }}CRUD(CRUDBase[{{ class_name }}Model, {{ class_name }}Creat
|
||||
def __init__(self, auth: AuthSchema) -> None:
|
||||
"""
|
||||
初始化CRUD数据层
|
||||
|
||||
|
||||
参数:
|
||||
- auth (AuthSchema): 认证信息模型
|
||||
"""
|
||||
super().__init__(model={{ class_name }}Model, auth=auth)
|
||||
|
||||
async def get_by_id_{{ business_name_slug }}_crud(self, id: int, preload: list | None = None) -> {{ class_name }}Model | None:
|
||||
"""
|
||||
详情
|
||||
|
||||
参数:
|
||||
- id (int): 对象ID
|
||||
- preload (list | None): 预加载关系,未提供时使用模型默认项
|
||||
|
||||
返回:
|
||||
- {{ class_name }}Model | None: 模型实例或None
|
||||
"""
|
||||
return await self.get(id=id, preload=preload)
|
||||
|
||||
async def list_{{ business_name_slug }}_crud(self, search: dict | None = None, order_by: list[dict] | None = None, preload: list | None = None) -> Sequence[{{ class_name }}Model]:
|
||||
"""
|
||||
列表查询
|
||||
|
||||
参数:
|
||||
- search (dict | None): 查询参数
|
||||
- order_by (list[dict] | None): 排序参数,未提供时使用模型默认项
|
||||
- preload (list | None): 预加载关系,未提供时使用模型默认项
|
||||
|
||||
返回:
|
||||
- Sequence[{{ class_name }}Model]: 模型实例序列
|
||||
"""
|
||||
return await self.list(search=search, order_by=order_by, preload=preload)
|
||||
|
||||
async def create_{{ business_name_slug }}_crud(self, data: {{ class_name }}CreateSchema) -> {{ class_name }}Model | None:
|
||||
"""
|
||||
创建
|
||||
|
||||
参数:
|
||||
- data ({{ class_name }}CreateSchema): 创建模型
|
||||
|
||||
返回:
|
||||
- {{ class_name }}Model | None: 模型实例或None
|
||||
"""
|
||||
return await self.create(data=data)
|
||||
|
||||
async def update_{{ business_name_slug }}_crud(self, id: int, data: {{ class_name }}UpdateSchema) -> {{ class_name }}Model | None:
|
||||
"""
|
||||
更新
|
||||
|
||||
参数:
|
||||
- id (int): 对象ID
|
||||
- data ({{ class_name }}UpdateSchema): 更新模型
|
||||
|
||||
返回:
|
||||
- {{ class_name }}Model | None: 模型实例或None
|
||||
"""
|
||||
return await self.update(id=id, data=data)
|
||||
|
||||
async def delete_{{ business_name_slug }}_crud(self, ids: list[int]) -> None:
|
||||
"""
|
||||
批量删除
|
||||
|
||||
参数:
|
||||
- ids (list[int]): 对象ID列表
|
||||
|
||||
返回:
|
||||
- None
|
||||
"""
|
||||
return await self.delete(ids=ids)
|
||||
|
||||
async def set_available_{{ business_name_slug }}_crud(self, ids: list[int], status: str) -> None:
|
||||
"""
|
||||
批量设置可用状态
|
||||
|
||||
参数:
|
||||
- ids (list[int]): 对象ID列表
|
||||
- status (str): 可用状态
|
||||
|
||||
返回:
|
||||
- None
|
||||
"""
|
||||
return await self.set(ids=ids, status=status)
|
||||
|
||||
async def page_{{ business_name_slug }}_crud(self, offset: int, limit: int, order_by: list[dict] | None = None, search: dict | None = None, preload: list | None = None) -> dict:
|
||||
"""
|
||||
分页查询
|
||||
|
||||
参数:
|
||||
- offset (int): 偏移量
|
||||
- limit (int): 每页数量
|
||||
- order_by (list[dict] | None): 排序参数,未提供时使用模型默认项
|
||||
- search (dict | None): 查询参数,未提供时查询所有
|
||||
- preload (list | None): 预加载关系,未提供时使用模型默认项
|
||||
|
||||
返回:
|
||||
- Dict: 分页数据
|
||||
"""
|
||||
order_by_list = order_by or [{'{{ pk_column_name }}': 'asc'}]
|
||||
search_dict = search or {}
|
||||
return await self.page(
|
||||
offset=offset,
|
||||
limit=limit,
|
||||
order_by=order_by_list,
|
||||
search=search_dict,
|
||||
out_schema={{ class_name }}OutSchema,
|
||||
preload=preload
|
||||
)
|
||||
@@ -7,7 +7,6 @@ from fastapi import UploadFile
|
||||
from app.api.v1.module_system.auth.schema import AuthSchema
|
||||
from app.core.base_schema import BatchSetAvailable
|
||||
from app.core.exceptions import CustomException
|
||||
from app.core.logger import log
|
||||
from app.utils.excel_util import ExcelUtil
|
||||
|
||||
from .crud import {{ class_name }}CRUD
|
||||
@@ -18,166 +17,166 @@ from .schema import (
|
||||
{{ class_name }}QueryParam
|
||||
)
|
||||
|
||||
|
||||
class {{ class_name }}Service:
|
||||
"""
|
||||
{{ function_name }}服务层
|
||||
"""
|
||||
|
||||
|
||||
@classmethod
|
||||
async def detail_{{ business_name_slug }}_service(cls, auth: AuthSchema, id: int) -> dict:
|
||||
"""
|
||||
详情
|
||||
|
||||
|
||||
参数:
|
||||
- auth: AuthSchema - 认证信息
|
||||
- id: int - 数据ID
|
||||
|
||||
|
||||
返回:
|
||||
- dict - 数据详情
|
||||
"""
|
||||
obj = await {{ class_name }}CRUD(auth).get_by_id_{{ business_name_slug }}_crud(id=id)
|
||||
obj = await {{ class_name }}CRUD(auth).get(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg="该数据不存在")
|
||||
return {{ class_name }}OutSchema.model_validate(obj).model_dump()
|
||||
|
||||
|
||||
@classmethod
|
||||
async def list_{{ business_name_slug }}_service(cls, auth: AuthSchema, search: {{ class_name }}QueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]:
|
||||
"""
|
||||
列表查询
|
||||
|
||||
|
||||
参数:
|
||||
- auth: AuthSchema - 认证信息
|
||||
- search: {{ class_name }}QueryParam | None - 查询参数
|
||||
- order_by: list[dict] | None - 排序参数
|
||||
|
||||
|
||||
返回:
|
||||
- list[dict] - 数据列表
|
||||
"""
|
||||
search_dict = search.__dict__ if search else None
|
||||
obj_list = await {{ class_name }}CRUD(auth).list_{{ business_name_slug }}_crud(search=search_dict, order_by=order_by)
|
||||
obj_list = await {{ class_name }}CRUD(auth).list(search=search_dict, order_by=order_by)
|
||||
return [{{ class_name }}OutSchema.model_validate(obj).model_dump() for obj in obj_list]
|
||||
|
||||
@classmethod
|
||||
async def page_{{ business_name_slug }}_service(cls, auth: AuthSchema, page_no: int, page_size: int, search: {{ class_name }}QueryParam | None = None, order_by: list[dict] | None = None) -> dict:
|
||||
"""
|
||||
分页查询(数据库分页)
|
||||
|
||||
|
||||
参数:
|
||||
- auth: AuthSchema - 认证信息
|
||||
- page_no: int - 页码
|
||||
- page_size: int - 每页数量
|
||||
- search: {{ class_name }}QueryParam | None - 查询参数
|
||||
- order_by: list[dict] | None - 排序参数
|
||||
|
||||
|
||||
返回:
|
||||
- dict - 分页查询结果
|
||||
"""
|
||||
search_dict = search.__dict__ if search else {}
|
||||
order_by_list = order_by or [{'{{ pk_column_name }}': 'asc'}]
|
||||
offset = (page_no - 1) * page_size
|
||||
result = await {{ class_name }}CRUD(auth).page_{{ business_name_slug }}_crud(
|
||||
result = await {{ class_name }}CRUD(auth).page(
|
||||
offset=offset,
|
||||
limit=page_size,
|
||||
order_by=order_by_list,
|
||||
search=search_dict
|
||||
search=search_dict,
|
||||
out_schema={{ class_name }}OutSchema
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@classmethod
|
||||
async def create_{{ business_name_slug }}_service(cls, auth: AuthSchema, data: {{ class_name }}CreateSchema) -> dict:
|
||||
"""
|
||||
创建
|
||||
|
||||
|
||||
参数:
|
||||
- auth: AuthSchema - 认证信息
|
||||
- data: {{ class_name }}CreateSchema - 创建数据
|
||||
|
||||
|
||||
返回:
|
||||
- dict - 创建结果
|
||||
"""
|
||||
{% for column in columns %}
|
||||
{% if column.is_unique == '1' %}
|
||||
{% if column.is_unique %}
|
||||
obj = await {{ class_name }}CRUD(auth).get({{ column.column_name }}=data.{{ column.column_name }})
|
||||
if obj:
|
||||
raise CustomException(msg='创建失败,{{ column.column_comment }}已存在')
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
obj = await {{ class_name }}CRUD(auth).create_{{ business_name_slug }}_crud(data=data)
|
||||
obj = await {{ class_name }}CRUD(auth).create(data=data)
|
||||
return {{ class_name }}OutSchema.model_validate(obj).model_dump()
|
||||
|
||||
|
||||
@classmethod
|
||||
async def update_{{ business_name_slug }}_service(cls, auth: AuthSchema, id: int, data: {{ class_name }}UpdateSchema) -> dict:
|
||||
"""
|
||||
更新
|
||||
|
||||
|
||||
参数:
|
||||
- auth: AuthSchema - 认证信息
|
||||
- id: int - 数据ID
|
||||
- data: {{ class_name }}UpdateSchema - 更新数据
|
||||
|
||||
|
||||
返回:
|
||||
- dict - 更新结果
|
||||
"""
|
||||
# 检查数据是否存在
|
||||
obj = await {{ class_name }}CRUD(auth).get_by_id_{{ business_name_slug }}_crud(id=id)
|
||||
obj = await {{ class_name }}CRUD(auth).get(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg='更新失败,该数据不存在')
|
||||
|
||||
|
||||
# 检查唯一性约束
|
||||
{% for column in columns %}
|
||||
{% if column.is_unique == '1' %}
|
||||
{% if column.is_unique %}
|
||||
exist_obj = await {{ class_name }}CRUD(auth).get({{ column.column_name }}=data.{{ column.column_name }})
|
||||
if exist_obj and getattr(exist_obj, '{{ pk_column_name }}') != id:
|
||||
raise CustomException(msg='更新失败,{{ column.column_comment }}重复')
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
obj = await {{ class_name }}CRUD(auth).update_{{ business_name_slug }}_crud(id=id, data=data)
|
||||
|
||||
obj = await {{ class_name }}CRUD(auth).update(id=id, data=data)
|
||||
return {{ class_name }}OutSchema.model_validate(obj).model_dump()
|
||||
|
||||
|
||||
@classmethod
|
||||
async def delete_{{ business_name_slug }}_service(cls, auth: AuthSchema, ids: list[int]) -> None:
|
||||
"""
|
||||
删除
|
||||
|
||||
|
||||
参数:
|
||||
- auth: AuthSchema - 认证信息
|
||||
- ids: list[int] - 数据ID列表
|
||||
|
||||
|
||||
返回:
|
||||
- None
|
||||
"""
|
||||
if len(ids) < 1:
|
||||
raise CustomException(msg='删除失败,删除对象不能为空')
|
||||
for id in ids:
|
||||
obj = await {{ class_name }}CRUD(auth).get_by_id_{{ business_name_slug }}_crud(id=id)
|
||||
obj = await {{ class_name }}CRUD(auth).get(id=id)
|
||||
if not obj:
|
||||
raise CustomException(msg=f'删除失败,ID为{id}的数据不存在')
|
||||
await {{ class_name }}CRUD(auth).delete_{{ business_name_slug }}_crud(ids=ids)
|
||||
|
||||
await {{ class_name }}CRUD(auth).delete(ids=ids)
|
||||
|
||||
@classmethod
|
||||
async def set_available_{{ business_name_slug }}_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
|
||||
"""
|
||||
批量设置状态
|
||||
|
||||
|
||||
参数:
|
||||
- auth: AuthSchema - 认证信息
|
||||
- data: BatchSetAvailable - 批量设置状态数据
|
||||
|
||||
|
||||
返回:
|
||||
- None
|
||||
"""
|
||||
await {{ class_name }}CRUD(auth).set_available_{{ business_name_slug }}_crud(ids=data.ids, status=data.status)
|
||||
|
||||
await {{ class_name }}CRUD(auth).set(ids=data.ids, status=data.status)
|
||||
|
||||
@classmethod
|
||||
async def batch_export_{{ business_name_slug }}_service(cls, obj_list: list[dict]) -> bytes:
|
||||
"""
|
||||
批量导出
|
||||
|
||||
|
||||
参数:
|
||||
- obj_list: list[dict] - 数据列表
|
||||
|
||||
|
||||
返回:
|
||||
- bytes - 导出的Excel文件内容
|
||||
"""
|
||||
@@ -190,7 +189,7 @@ class {{ class_name }}Service:
|
||||
data = obj_list.copy()
|
||||
for item in data:
|
||||
# 处理状态
|
||||
item["status"] = "启用" if item.get("status") == "0" else "停用"
|
||||
item["status"] = "启用" if item.get("status") == 0 else "停用"
|
||||
# 处理创建者
|
||||
creator_info = item.get("created_id")
|
||||
if isinstance(creator_info, dict):
|
||||
@@ -204,12 +203,12 @@ class {{ class_name }}Service:
|
||||
async def batch_import_{{ business_name_slug }}_service(cls, auth: AuthSchema, file: UploadFile, update_support: bool = False) -> str:
|
||||
"""
|
||||
批量导入
|
||||
|
||||
|
||||
参数:
|
||||
- auth: AuthSchema - 认证信息
|
||||
- file: UploadFile - 上传的Excel文件
|
||||
- update_support: bool - 是否支持更新存在数据
|
||||
|
||||
|
||||
返回:
|
||||
- str - 导入结果信息
|
||||
"""
|
||||
@@ -235,25 +234,25 @@ class {{ class_name }}Service:
|
||||
|
||||
# 重命名列名
|
||||
df.rename(columns=header_dict, inplace=True)
|
||||
|
||||
# 验证必填字段
|
||||
|
||||
# 验证必填字段(非主键且不允许为空的列)
|
||||
{% for column in columns %}
|
||||
{% if column.required == '1' %}
|
||||
{% if column.is_nullable is false and column.is_pk is false %}
|
||||
errors = []
|
||||
missing_rows = df[df['{{ column.column_name }}'].isnull()].index.tolist()
|
||||
if missing_rows:
|
||||
field_name = [k for k,v in header_dict.items() if v == field][0]
|
||||
field_name = [k for k,v in header_dict.items() if v == '{{ column.column_name }}'][0]
|
||||
rows_str = "、".join([str(i+1) for i in missing_rows])
|
||||
errors.append(f"{field_name}不能为空,第{rows_str}行")
|
||||
if errors:
|
||||
raise CustomException(msg=f"导入失败,以下行缺少必要字段:\n{'; '.join(errors)}")
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
error_msgs = []
|
||||
success_count = 0
|
||||
count = 0
|
||||
|
||||
|
||||
for _index, row in df.iterrows():
|
||||
count += 1
|
||||
try:
|
||||
@@ -264,10 +263,10 @@ class {{ class_name }}Service:
|
||||
}
|
||||
# 使用CreateSchema做校验后入库
|
||||
create_schema = {{ class_name }}CreateSchema.model_validate(data)
|
||||
|
||||
|
||||
# 检查唯一性约束
|
||||
{% for column in columns %}
|
||||
{% if column.is_unique == '1' %}
|
||||
{% if column.is_unique %}
|
||||
exists_obj = await {{ class_name }}CRUD(auth).get({{ column.column_name }}=create_schema.{{ column.column_name }})
|
||||
if exists_obj:
|
||||
if update_support:
|
||||
@@ -278,8 +277,8 @@ class {{ class_name }}Service:
|
||||
continue
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
await {{ class_name }}CRUD(auth).create_{{ business_name_slug }}_crud(data=create_schema)
|
||||
|
||||
await {{ class_name }}CRUD(auth).create(data=create_schema)
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
error_msgs.append(f"第{count}行: {str(e)}")
|
||||
@@ -289,16 +288,16 @@ class {{ class_name }}Service:
|
||||
if error_msgs:
|
||||
result += "\n错误信息:\n" + "\n".join(error_msgs)
|
||||
return result
|
||||
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"批量导入失败: {str(e)}")
|
||||
logger.error(f"批量导入失败: {str(e)}")
|
||||
raise CustomException(msg=f"导入失败: {str(e)}")
|
||||
|
||||
|
||||
@classmethod
|
||||
async def import_template_download_{{ business_name_slug }}_service(cls) -> bytes:
|
||||
"""
|
||||
下载导入模板
|
||||
|
||||
|
||||
返回:
|
||||
- bytes - Excel文件的二进制数据
|
||||
"""
|
||||
@@ -309,16 +308,16 @@ class {{ class_name }}Service:
|
||||
]
|
||||
selector_header_list = []
|
||||
option_list = []
|
||||
|
||||
|
||||
{% for column in columns %}
|
||||
{% if column.html_type == 'select' and column.dict_type %}
|
||||
selector_header_list.append('{{ column.column_comment }}')
|
||||
option_list.append({'{{ column.column_comment }}': []})
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
return ExcelUtil.get_excel_template(
|
||||
header_list=header_list,
|
||||
selector_header_list=selector_header_list,
|
||||
option_list=option_list
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import request from "@utils/http";
|
||||
import { request } from "@utils";
|
||||
|
||||
// API 前缀来自分系统包 module_xxx → /xxx
|
||||
// 对齐 module_example/demo:业务接口固定为 /{prefix}/{module_name}
|
||||
const API_PATH = "/{{ api_route_prefix }}/{{ module_name }}";
|
||||
|
||||
const {{ class_name }}API = {
|
||||
/** 获取列表 */
|
||||
get{{ class_name }}List(query: {{ class_name }}PageQuery) {
|
||||
return request<ApiResponse<PageResult<{{ class_name }}Table>>>({
|
||||
url: `${API_PATH}/list`,
|
||||
@@ -14,15 +13,13 @@ const {{ class_name }}API = {
|
||||
});
|
||||
},
|
||||
|
||||
/** 获取详情 */
|
||||
get{{ class_name }}Detail(id: number) {
|
||||
get{{ class_name }}Detail(query: number) {
|
||||
return request<ApiResponse<{{ class_name }}Table>>({
|
||||
url: `${API_PATH}/detail/${id}`,
|
||||
url: `${API_PATH}/detail/${query}`,
|
||||
method: "get",
|
||||
});
|
||||
},
|
||||
|
||||
/** 新增 */
|
||||
create{{ class_name }}(body: {{ class_name }}Form) {
|
||||
return request<ApiResponse>({
|
||||
url: `${API_PATH}/create`,
|
||||
@@ -31,7 +28,6 @@ const {{ class_name }}API = {
|
||||
});
|
||||
},
|
||||
|
||||
/** 修改 */
|
||||
update{{ class_name }}(id: number, body: {{ class_name }}Form) {
|
||||
return request<ApiResponse>({
|
||||
url: `${API_PATH}/update/${id}`,
|
||||
@@ -40,44 +36,39 @@ const {{ class_name }}API = {
|
||||
});
|
||||
},
|
||||
|
||||
/** 删除(支持批量) */
|
||||
delete{{ class_name }}(ids: number[]) {
|
||||
delete{{ class_name }}(body: number[]) {
|
||||
return request<ApiResponse>({
|
||||
url: `${API_PATH}/delete`,
|
||||
method: "delete",
|
||||
data: ids,
|
||||
data: body,
|
||||
});
|
||||
},
|
||||
|
||||
/** 批量启用/停用 */
|
||||
batch{{ class_name }}(body: BatchType) {
|
||||
return request<ApiResponse>({
|
||||
url: `${API_PATH}/status/batch`,
|
||||
url: `${API_PATH}/available/setting`,
|
||||
method: "patch",
|
||||
data: body,
|
||||
});
|
||||
},
|
||||
|
||||
/** 导出 Excel */
|
||||
export{{ class_name }}(query: {{ class_name }}PageQuery) {
|
||||
export{{ class_name }}(body: {{ class_name }}PageQuery) {
|
||||
return request<Blob>({
|
||||
url: `${API_PATH}/export`,
|
||||
method: "post",
|
||||
data: query,
|
||||
data: body,
|
||||
responseType: "blob",
|
||||
});
|
||||
},
|
||||
|
||||
/** 下载导入模板 */
|
||||
downloadTemplate{{ class_name }}() {
|
||||
return request<Blob>({
|
||||
return request<ApiResponse>({
|
||||
url: `${API_PATH}/download/template`,
|
||||
method: "post",
|
||||
responseType: "blob",
|
||||
});
|
||||
},
|
||||
|
||||
/** 导入 Excel */
|
||||
import{{ class_name }}(body: FormData) {
|
||||
return request<ApiResponse>({
|
||||
url: `${API_PATH}/import`,
|
||||
@@ -100,7 +91,7 @@ export default {{ class_name }}API;
|
||||
export interface {{ class_name }}PageQuery extends PageQuery {
|
||||
{% for column in columns %}
|
||||
{# 主键列默认不参与查询 #}
|
||||
{% if column.is_query and column.column_name != pk_column_name and column.query_type != "BETWEEN" and column.column_name not in ['created_time', 'updated_time'] %}
|
||||
{% if column.is_query and column.column_name != pk_column_name and column.column_name not in ['created_time', 'updated_time'] %}
|
||||
{{ column.column_name }}?: {{
|
||||
'string' if column.query_type == 'LIKE'
|
||||
else (column.python_type | python_to_ts_type)
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
:perm-patch="['{{ permission_prefix }}:patch']"
|
||||
:delete-loading="batchDeleting"
|
||||
@add="openEditDialog('add')"
|
||||
@import="openImportModal"
|
||||
@export="openExportModal"
|
||||
@import="openImport"
|
||||
@export="openExport"
|
||||
@delete="handleBatchDelete"
|
||||
@more="runBatchStatus"
|
||||
/>
|
||||
@@ -65,73 +65,37 @@
|
||||
width="920px"
|
||||
dialog-class="crud-embed-dialog"
|
||||
modal-class="crud-embed-dialog"
|
||||
@close="handleCloseDialog"
|
||||
:form-mode="dialogVisible.type"
|
||||
:confirm-loading="submitLoading"
|
||||
@cancel="handleCloseDialog"
|
||||
@confirm="dialogVisible.type === 'detail' ? handleCloseDialog() : handleSubmit()"
|
||||
>
|
||||
<template v-if="dialogVisible.type === 'detail'">
|
||||
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
|
||||
<ElDescriptions :column="4" border>
|
||||
{% for column in columns %}
|
||||
{% set column_comment = column.column_comment if column.column_comment else '' %}
|
||||
{% set parentheseIndex = column_comment.find("(") %}
|
||||
{% set comment = column_comment[:parentheseIndex] if parentheseIndex != -1 else column_comment %}
|
||||
{% if column.column_name == 'status' %}
|
||||
<ElDescriptionsItem label="状态" :span="2">
|
||||
<ElTag :type="detailFormData.status === '0' ? 'success' : 'danger'">
|
||||
{{ '{{' }} detailFormData.status === "0" ? "启用" : "停用" {{ '}}' }}
|
||||
</ElTag>
|
||||
</ElDescriptionsItem>
|
||||
{% elif column.column_name == 'created_id' %}
|
||||
<ElDescriptionsItem label="创建人" :span="2">
|
||||
{{ '{{' }} detailFormData.created_by?.name {{ '}}' }}
|
||||
</ElDescriptionsItem>
|
||||
{% elif column.column_name == 'updated_id' %}
|
||||
<ElDescriptionsItem label="更新人" :span="2">
|
||||
{{ '{{' }} detailFormData.updated_by?.name {{ '}}' }}
|
||||
</ElDescriptionsItem>
|
||||
{% elif column.column_name == 'created_time' %}
|
||||
<ElDescriptionsItem label="创建时间" :span="2">
|
||||
{{ '{{' }} detailFormData.created_time {{ '}}' }}
|
||||
</ElDescriptionsItem>
|
||||
{% elif column.column_name == 'updated_time' %}
|
||||
<ElDescriptionsItem label="更新时间" :span="2">
|
||||
{{ '{{' }} detailFormData.updated_time {{ '}}' }}
|
||||
</ElDescriptionsItem>
|
||||
{% else %}
|
||||
<ElDescriptionsItem label="{{ comment }}" :span="2">
|
||||
{{ '{{' }} detailFormData.{{ column.column_name }} {{ '}}' }}
|
||||
</ElDescriptionsItem>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ElDescriptions>
|
||||
{% if table.sub %}
|
||||
<ElDivider>关联{{ table.sub_table.function_name }}</ElDivider>
|
||||
<ElTable :data="subTableData" size="small" border>
|
||||
{% for col in table.sub_table.columns %}
|
||||
{% if col.is_list and col.column_name != table.sub_table.pk_column.column_name %}
|
||||
{% set col_comment = col.column_comment if col.column_comment else col.column_name %}
|
||||
<ElTableColumn prop="{{ col.column_name }}" label="{{ col_comment }}" />
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ElTable>
|
||||
{% endif %}
|
||||
</ElScrollbar>
|
||||
<FaDescriptions
|
||||
:column="4"
|
||||
:data="detailFormData"
|
||||
:items="detailItems"
|
||||
max-height="70vh"
|
||||
/>
|
||||
</template>
|
||||
<template v-else>
|
||||
<ElScrollbar max-height="70vh" :view-style="{ overflowX: 'hidden' }">
|
||||
<FaForm
|
||||
ref="dataFormRef"
|
||||
v-model="formData"
|
||||
:items="dialogFormItems"
|
||||
:rules="rules"
|
||||
label-suffix=":"
|
||||
:label-width="100"
|
||||
label-position="right"
|
||||
:span="24"
|
||||
:gutter="16"
|
||||
:show-reset="false"
|
||||
:show-submit="false"
|
||||
class="crud-dialog-art-form"
|
||||
/>
|
||||
<FaForm
|
||||
:key="formRenderKey"
|
||||
scrollbar
|
||||
max-height="70vh"
|
||||
ref="dataFormRef"
|
||||
v-model="formData"
|
||||
:items="dialogFormItems"
|
||||
:rules="rules"
|
||||
label-suffix=":"
|
||||
:label-width="100"
|
||||
label-position="right"
|
||||
:span="24"
|
||||
:gutter="16"
|
||||
:show-reset="false"
|
||||
:show-submit="false"
|
||||
class="crud-dialog-art-form"
|
||||
/>
|
||||
{% if table.sub %}
|
||||
<ElDivider>{{ table.sub_table.function_name }}列表</ElDivider>
|
||||
<div class="sub-table-section">
|
||||
@@ -172,14 +136,14 @@
|
||||
</FaDialog>
|
||||
|
||||
<FaImportDialog
|
||||
v-model="importModalVisible"
|
||||
v-model="importVisible"
|
||||
:content-config="importContentConfig"
|
||||
default-template-file-name="{{ module_name }}_import_template.xlsx"
|
||||
@upload="handleCrudImportUpload"
|
||||
/>
|
||||
|
||||
<FaExportDialog
|
||||
v-model="exportModalVisible"
|
||||
v-model="exportVisible"
|
||||
:content-config="exportContentConfig"
|
||||
:query-params="exportQueryParams"
|
||||
:page-data="data"
|
||||
@@ -191,34 +155,24 @@
|
||||
<script setup lang="ts">
|
||||
import { h, computed, ref, reactive, onMounted } from "vue";
|
||||
import { useAuth } from "@/hooks/core/useAuth";
|
||||
import { renderTableOperationCell, type TableOperationAction } from "@utils/table";
|
||||
import { renderTableOperationCell, type TableOperationAction } from "@/utils/table";
|
||||
import { useTable } from "@/hooks/core/useTable";
|
||||
import FaTableHeaderLeft from "@/components/tables/fa-table-header-left/index.vue";
|
||||
import FaImportDialog from "@/components/modal/fa-import-dialog/index.vue";
|
||||
import FaExportDialog from "@/components/modal/fa-export-dialog/index.vue";
|
||||
import { useImportExport } from "@/hooks/core/useImportExport";
|
||||
import { useCrudDialog } from "@/hooks/core/useCrudDialog";
|
||||
import { useTableSelection } from "@/hooks/core/useTableSelection";
|
||||
import { cleanEmptyArrayParams, stripPaginationParams } from "@/utils/query";
|
||||
import type { IContentConfig, IObject } from "@/components/modal/types";
|
||||
import FaSearchBarWithAudit from "@/components/forms/fa-search-bar/FaSearchBarWithAudit.vue";
|
||||
import type { AuditSearchFormParams } from "@/components/forms/fa-search-bar/auditSearchFormItems";
|
||||
import FaDialog from "@/components/modal/fa-dialog/index.vue";
|
||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||
import type { ColumnOption } from "@/types/component";
|
||||
import {{ class_name }}API, {
|
||||
type {{ class_name }}Form,
|
||||
type {{ class_name }}PageQuery,
|
||||
type {{ class_name }}Table,
|
||||
} from "@/api/{{ package_name }}/{{ module_name }}";
|
||||
import { ElMessage, ElMessageBox, ElTag } from "element-plus";
|
||||
import { ElTag, ElMessage } from "element-plus";
|
||||
import { useDictStore } from "@/stores/modules/dict";
|
||||
import { ResultEnum } from "@/enums/api/result.enum";
|
||||
import FaForm from "@/components/forms/fa-form/index.vue";
|
||||
import type { FormItem } from "@/components/forms/fa-form/index.vue";
|
||||
{% set _img = namespace(need=false) %}
|
||||
{% for column in columns %}
|
||||
{% if (column.is_insert or column.is_edit) and column.html_type == "imageUpload" %}
|
||||
{% set _img.need = true %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if _img.need %}
|
||||
import SingleImageUpload from "@/components/Upload/SingleImageUpload.vue";
|
||||
{% endif %}
|
||||
|
||||
defineOptions({
|
||||
name: "{{ class_name }}",
|
||||
@@ -226,6 +180,7 @@ defineOptions({
|
||||
});
|
||||
|
||||
const { hasAuth } = useAuth();
|
||||
const dictStore = useDictStore();
|
||||
|
||||
type {{ class_name }}SearchFormParams = {
|
||||
{% for column in columns %}
|
||||
@@ -374,7 +329,7 @@ const {
|
||||
label: "状态",
|
||||
width: 88,
|
||||
formatter: (row: {{ class_name }}Table) => {
|
||||
const ok = row.status === "0";
|
||||
const ok = row.status === 0;
|
||||
const cfg = ok
|
||||
? { type: "success" as const, text: "启用" }
|
||||
: { type: "info" as const, text: "停用" };
|
||||
@@ -487,14 +442,33 @@ const exportContentConfig = computed(() => ({
|
||||
},
|
||||
}));
|
||||
|
||||
const dialogVisible = reactive({
|
||||
title: "",
|
||||
visible: false,
|
||||
type: "create" as "create" | "update" | "detail",
|
||||
});
|
||||
const { dialogVisible } = useCrudDialog();
|
||||
|
||||
const detailFormData = ref<{{ class_name }}Table>({});
|
||||
|
||||
const detailItems: import("@/components/others/fa-descriptions/index.vue").DescriptionsItem[] = [
|
||||
{% for column in columns %}
|
||||
{% if column.is_list %}
|
||||
{% set column_comment = column.column_comment if column.column_comment else '' %}
|
||||
{% set parentheseIndex = column_comment.find("(") %}
|
||||
{% set comment = column_comment[:parentheseIndex] if parentheseIndex != -1 else column_comment %}
|
||||
{% if column.column_name == 'status' %}
|
||||
{ label: "状态", prop: "status", tag: { map: { "0": { type: "success", text: "启用" }, "1": { type: "danger", text: "停用" } } } },
|
||||
{% elif column.column_name == 'created_id' %}
|
||||
{ label: "创建人", prop: "created_by.name" },
|
||||
{% elif column.column_name == 'updated_id' %}
|
||||
{ label: "更新人", prop: "updated_by.name" },
|
||||
{% elif column.column_name == 'created_time' %}
|
||||
{ label: "创建时间", prop: "created_time" },
|
||||
{% elif column.column_name == 'updated_time' %}
|
||||
{ label: "更新时间", prop: "updated_time" },
|
||||
{% else %}
|
||||
{ label: "{{ comment }}", prop: "{{ column.column_name }}" },
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
];
|
||||
|
||||
const formData = ref<{{ class_name }}Form>({
|
||||
{% for column in columns %}
|
||||
{% if column.is_insert or column.is_edit %}
|
||||
@@ -559,9 +533,15 @@ const dialogFormItems = computed<FormItem[]>(() => [
|
||||
{% endfor %}
|
||||
]);
|
||||
|
||||
const dataFormRef = ref();
|
||||
const importModalVisible = ref(false);
|
||||
const exportModalVisible = ref(false);
|
||||
const dataFormRef = ref<{
|
||||
resetFields: () => void;
|
||||
clearValidate: () => void;
|
||||
validate: (cb: (valid: boolean) => void) => void;
|
||||
} | null>(null);
|
||||
const submitLoading = ref(false);
|
||||
const formRenderKey = ref(0);
|
||||
|
||||
const { importVisible, exportVisible, openImport, openExport } = useImportExport();
|
||||
|
||||
const initialFormData: {{ class_name }}Form = {
|
||||
{% for column in columns %}
|
||||
@@ -622,7 +602,6 @@ function buildRowActions(row: {{ class_name }}Table): TableOperationAction[] {
|
||||
key: "detail",
|
||||
label: "详情",
|
||||
artType: "view",
|
||||
icon: "ri:file-list-3-line",
|
||||
perm: "{{ permission_prefix }}:detail",
|
||||
run: () => void openDetailDialog(row),
|
||||
},
|
||||
@@ -673,8 +652,10 @@ async function openEditDialog(type: "add" | "edit", row?: {{ class_name }}Table)
|
||||
{% if table.sub %}
|
||||
subTableData.value = [];
|
||||
{% endif %}
|
||||
formRenderKey.value += 1;
|
||||
} else if (row?.[PK]) {
|
||||
dialogVisible.title = "修改";
|
||||
formRenderKey.value += 1;
|
||||
const response = await {{ class_name }}API.get{{ class_name }}Detail(row[PK] as number);
|
||||
Object.assign(formData.value, response.data.data);
|
||||
{% if table.sub %}
|
||||
@@ -698,11 +679,12 @@ async function handleCloseDialog() {
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
dataFormRef.value.validate(async (valid: boolean) => {
|
||||
dataFormRef.value?.validate(async (valid: boolean) => {
|
||||
if (!valid) return;
|
||||
const submitData = { ...formData.value };
|
||||
const id = formData.value[PK] as number | undefined;
|
||||
try {
|
||||
submitLoading.value = true;
|
||||
if (id) {
|
||||
await {{ class_name }}API.update{{ class_name }}(id, { [PK]: id, ...submitData });
|
||||
await refreshUpdate();
|
||||
@@ -714,6 +696,8 @@ async function handleSubmit() {
|
||||
await resetForm();
|
||||
} catch (error: unknown) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -803,10 +787,6 @@ async function runBatchStatus(status: string) {
|
||||
}
|
||||
}
|
||||
|
||||
function openImportModal() {
|
||||
importModalVisible.value = true;
|
||||
}
|
||||
|
||||
async function handleCrudImportUpload(uploadFormData: FormData) {
|
||||
try {
|
||||
const res = await {{ class_name }}API.import{{ class_name }}(uploadFormData);
|
||||
@@ -815,7 +795,7 @@ async function handleCrudImportUpload(uploadFormData: FormData) {
|
||||
return;
|
||||
}
|
||||
ElMessage.success(res.data.msg || "导入成功");
|
||||
importModalVisible.value = false;
|
||||
importVisible.value = false;
|
||||
await refreshData();
|
||||
} catch (error) {
|
||||
console.error("[Import]", error);
|
||||
@@ -823,10 +803,6 @@ async function handleCrudImportUpload(uploadFormData: FormData) {
|
||||
}
|
||||
}
|
||||
|
||||
function openExportModal() {
|
||||
exportModalVisible.value = true;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
{% for column in columns %}
|
||||
{% if column.is_query and column.dict_type and (column.html_type == "select" or column.html_type == "radio") %}
|
||||
|
||||
@@ -100,7 +100,7 @@ class Jinja2TemplateUtil:
|
||||
@classmethod
|
||||
def business_name_to_slug(cls, business_name: str | None) -> str:
|
||||
"""
|
||||
业务路径可含斜杠(如 ``demo/demo01``)用于目录与路由前缀;
|
||||
业务路径可含斜杠(如 ``demo/subdir``)用于目录与路由前缀;
|
||||
Python 函数/方法名仅使用最后一段并规范为合法 snake_case 片段。
|
||||
|
||||
参数:
|
||||
|
||||
Reference in New Issue
Block a user