Merge pull request #332 from fastapiadmin/dev

Dev
This commit is contained in:
fastapiadmin
2026-03-16 01:02:45 +08:00
committed by GitHub
7 changed files with 453 additions and 200 deletions
@@ -16,23 +16,50 @@ from .schema import {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema,
{{ class_name }}Router = APIRouter(prefix='/{{ business_name }}', tags=["{{ function_name }}模块"]) {{ class_name }}Router = APIRouter(prefix='/{{ business_name }}', tags=["{{ function_name }}模块"])
@{{ class_name }}Router.get("/detail/{id}", summary="获取{{ function_name }}详情", description="获取{{ function_name }}详情") @{{ class_name }}Router.get(
"/detail/{id}",
summary="获取{{ function_name }}详情",
description="获取{{ function_name }}详情"
)
async def get_{{ business_name }}_detail_controller( async def get_{{ business_name }}_detail_controller(
id: int = Path(..., description="ID"), id: int = Path(..., description="ID"),
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:query"])) auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:query"]))
) -> JSONResponse: ) -> JSONResponse:
"""获取{{ function_name }}详情接口""" """
获取{{ function_name }}详情接口
参数:
- id: int - 数据ID
- auth: AuthSchema - 认证信息
返回:
- JSONResponse - 包含{{ function_name }}详情的JSON响应
"""
result_dict = await {{ class_name }}Service.detail_{{ business_name }}_service(auth=auth, id=id) result_dict = await {{ class_name }}Service.detail_{{ business_name }}_service(auth=auth, id=id)
log.info(f"获取{{ function_name }}详情成功 {id}") log.info(f"获取{{ function_name }}详情成功 {id}")
return SuccessResponse(data=result_dict, msg="获取{{ function_name }}详情成功") return SuccessResponse(data=result_dict, msg="获取{{ function_name }}详情成功")
@{{ class_name }}Router.get("/list", summary="查询{{ function_name }}列表", description="查询{{ function_name }}列表") @{{ class_name }}Router.get(
"/list",
summary="查询{{ function_name }}列表",
description="查询{{ function_name }}列表"
)
async def get_{{ business_name }}_list_controller( async def get_{{ business_name }}_list_controller(
page: PaginationQueryParam = Depends(), page: PaginationQueryParam = Depends(),
search: {{ class_name }}QueryParam = Depends(), search: {{ class_name }}QueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:query"])) auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:query"]))
) -> JSONResponse: ) -> JSONResponse:
"""查询{{ function_name }}列表接口(数据库分页)""" """
查询{{ function_name }}列表接口(数据库分页)
参数:
- page: PaginationQueryParam - 分页参数
- search: {{ class_name }}QueryParam - 查询参数
- auth: AuthSchema - 认证信息
返回:
- JSONResponse - 包含{{ function_name }}列表的JSON响应
"""
result_dict = await {{ class_name }}Service.page_{{ business_name }}_service( result_dict = await {{ class_name }}Service.page_{{ business_name }}_service(
auth=auth, auth=auth,
page_no=page.page_no if page.page_no is not None else 1, page_no=page.page_no if page.page_no is not None else 1,
@@ -43,77 +70,164 @@ async def get_{{ business_name }}_list_controller(
log.info("查询{{ function_name }}列表成功") log.info("查询{{ function_name }}列表成功")
return SuccessResponse(data=result_dict, msg="查询{{ function_name }}列表成功") return SuccessResponse(data=result_dict, msg="查询{{ function_name }}列表成功")
@{{ class_name }}Router.post("/create", summary="创建{{ function_name }}", description="创建{{ function_name }}") @{{ class_name }}Router.post(
"/create",
summary="创建{{ function_name }}",
description="创建{{ function_name }}"
)
async def create_{{ business_name }}_controller( async def create_{{ business_name }}_controller(
data: {{ class_name }}CreateSchema, data: {{ class_name }}CreateSchema,
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:create"])) auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:create"]))
) -> JSONResponse: ) -> JSONResponse:
"""创建{{ function_name }}接口""" """
创建{{ function_name }}接口
参数:
- data: {{ class_name }}CreateSchema - 创建数据
- auth: AuthSchema - 认证信息
返回:
- JSONResponse - 包含创建{{ function_name }}结果的JSON响应
"""
result_dict = await {{ class_name }}Service.create_{{ business_name }}_service(auth=auth, data=data) result_dict = await {{ class_name }}Service.create_{{ business_name }}_service(auth=auth, data=data)
log.info("创建{{ function_name }}成功") log.info("创建{{ function_name }}成功")
return SuccessResponse(data=result_dict, msg="创建{{ function_name }}成功") return SuccessResponse(data=result_dict, msg="创建{{ function_name }}成功")
@{{ class_name }}Router.put("/update/{id}", summary="修改{{ function_name }}", description="修改{{ function_name }}") @{{ class_name }}Router.put(
"/update/{id}",
summary="修改{{ function_name }}",
description="修改{{ function_name }}"
)
async def update_{{ business_name }}_controller( async def update_{{ business_name }}_controller(
data: {{ class_name }}UpdateSchema, data: {{ class_name }}UpdateSchema,
id: int = Path(..., description="ID"), id: int = Path(..., description="ID"),
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:update"])) auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:update"]))
) -> JSONResponse: ) -> JSONResponse:
"""修改{{ function_name }}接口""" """
修改{{ function_name }}接口
参数:
- id: int - 数据ID
- data: {{ class_name }}UpdateSchema - 更新数据
- auth: AuthSchema - 认证信息
返回:
- JSONResponse - 包含修改{{ function_name }}结果的JSON响应
"""
result_dict = await {{ class_name }}Service.update_{{ business_name }}_service(auth=auth, id=id, data=data) result_dict = await {{ class_name }}Service.update_{{ business_name }}_service(auth=auth, id=id, data=data)
log.info("修改{{ function_name }}成功") log.info("修改{{ function_name }}成功")
return SuccessResponse(data=result_dict, msg="修改{{ function_name }}成功") return SuccessResponse(data=result_dict, msg="修改{{ function_name }}成功")
@{{ class_name }}Router.delete("/delete", summary="删除{{ function_name }}", description="删除{{ function_name }}") @{{ class_name }}Router.delete(
"/delete",
summary="删除{{ function_name }}",
description="删除{{ function_name }}"
)
async def delete_{{ business_name }}_controller( async def delete_{{ business_name }}_controller(
ids: list[int] = Body(..., description="ID列表"), ids: list[int] = Body(..., description="ID列表"),
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:delete"])) auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:delete"]))
) -> JSONResponse: ) -> JSONResponse:
"""删除{{ function_name }}接口""" """
删除{{ function_name }}接口
参数:
- ids: list[int] - 数据ID列表
- auth: AuthSchema - 认证信息
返回:
- JSONResponse - 包含删除{{ function_name }}结果的JSON响应
"""
await {{ class_name }}Service.delete_{{ business_name }}_service(auth=auth, ids=ids) await {{ class_name }}Service.delete_{{ business_name }}_service(auth=auth, ids=ids)
log.info(f"删除{{ function_name }}成功: {ids}") log.info(f"删除{{ function_name }}成功: {ids}")
return SuccessResponse(msg="删除{{ function_name }}成功") return SuccessResponse(msg="删除{{ function_name }}成功")
@{{ class_name }}Router.patch("/available/setting", summary="批量修改{{ function_name }}状态", description="批量修改{{ function_name }}状态") @{{ class_name }}Router.patch(
"/available/setting",
summary="批量修改{{ function_name }}状态",
description="批量修改{{ function_name }}状态"
)
async def batch_set_available_{{ business_name }}_controller( async def batch_set_available_{{ business_name }}_controller(
data: BatchSetAvailable, data: BatchSetAvailable,
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:patch"])) auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:patch"]))
) -> JSONResponse: ) -> JSONResponse:
"""批量修改{{ function_name }}状态接口""" """
批量修改{{ function_name }}状态接口
参数:
- data: BatchSetAvailable - 批量修改状态数据
- auth: AuthSchema - 认证信息
返回:
- JSONResponse - 包含批量修改{{ function_name }}状态结果的JSON响应
"""
await {{ class_name }}Service.set_available_{{ business_name }}_service(auth=auth, data=data) await {{ class_name }}Service.set_available_{{ business_name }}_service(auth=auth, data=data)
log.info(f"批量修改{{ function_name }}状态成功: {data.ids}") log.info(f"批量修改{{ function_name }}状态成功: {data.ids}")
return SuccessResponse(msg="批量修改{{ function_name }}状态成功") return SuccessResponse(msg="批量修改{{ function_name }}状态成功")
@{{ class_name }}Router.post('/export', summary="导出{{ function_name }}", description="导出{{ function_name }}") @{{ class_name }}Router.post(
'/export',
summary="导出{{ function_name }}",
description="导出{{ function_name }}"
)
async def export_{{ business_name }}_list_controller( async def export_{{ business_name }}_list_controller(
search: {{ class_name }}QueryParam = Depends(), search: {{ class_name }}QueryParam = Depends(),
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:export"])) auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:export"]))
) -> StreamingResponse: ) -> StreamingResponse:
"""导出{{ function_name }}接口""" """
导出{{ function_name }}接口
参数:
- search: {{ class_name }}QueryParam - 查询参数
- auth: AuthSchema - 认证信息
返回:
- StreamingResponse - 包含导出{{ function_name }}数据的流式响应
"""
result_dict_list = await {{ class_name }}Service.list_{{ business_name }}_service(search=search, auth=auth) result_dict_list = await {{ class_name }}Service.list_{{ business_name }}_service(search=search, auth=auth)
export_result = await {{ class_name }}Service.batch_export_{{ business_name }}_service(obj_list=result_dict_list) export_result = await {{ class_name }}Service.batch_export_{{ business_name }}_service(obj_list=result_dict_list)
log.info('导出{{ function_name }}成功') log.info('导出{{ function_name }}成功')
return StreamResponse( return StreamResponse(
data=bytes2file_response(export_result), data=bytes2file_response(export_result),
media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', media_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
headers={ headers={'Content-Disposition': 'attachment; filename={{ table_name }}.xlsx'}
'Content-Disposition': 'attachment; filename={{ table_name }}.xlsx'
}
) )
@{{ class_name }}Router.post('/import', summary="导入{{ function_name }}", description="导入{{ function_name }}") @{{ class_name }}Router.post(
'/import',
summary="导入{{ function_name }}",
description="导入{{ function_name }}"
)
async def import_{{ business_name }}_list_controller( async def import_{{ business_name }}_list_controller(
file: UploadFile, file: UploadFile,
auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:import"])) auth: AuthSchema = Depends(AuthPermission(["{{ permission_prefix }}:import"]))
) -> JSONResponse: ) -> JSONResponse:
"""导入{{ function_name }}接口""" """
导入{{ function_name }}接口
参数:
- file: UploadFile - 上传的Excel文件
- auth: AuthSchema - 认证信息
返回:
- JSONResponse - 包含导入{{ function_name }}结果的JSON响应
"""
batch_import_result = await {{ class_name }}Service.batch_import_{{ business_name }}_service(file=file, auth=auth, update_support=True) batch_import_result = await {{ class_name }}Service.batch_import_{{ business_name }}_service(file=file, auth=auth, update_support=True)
log.info("导入{{ function_name }}成功") log.info("导入{{ function_name }}成功")
return SuccessResponse(data=batch_import_result, msg="导入{{ 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"]))]) @{{ class_name }}Router.post(
'/download/template',
summary="获取{{ function_name }}导入模板",
description="获取{{ function_name }}导入模板",
dependencies=[Depends(AuthPermission(["{{ permission_prefix }}:download"]))]
)
async def export_{{ business_name }}_template_controller() -> StreamingResponse: async def export_{{ business_name }}_template_controller() -> StreamingResponse:
"""获取{{ function_name }}导入模板接口""" """
获取{{ function_name }}导入模板接口
返回:
- StreamingResponse - 包含{{ function_name }}导入模板的流式响应
"""
import_template_result = await {{ class_name }}Service.import_template_download_{{ business_name }}_service() import_template_result = await {{ class_name }}Service.import_template_download_{{ business_name }}_service()
log.info('获取{{ function_name }}导入模板成功') log.info('获取{{ function_name }}导入模板成功')
return StreamResponse( return StreamResponse(
@@ -11,6 +11,7 @@ from fastapi import Query
{% if table.created_time %} {% if table.created_time %}
from app.core.validator import DateTimeStr from app.core.validator import DateTimeStr
{% endif %} {% endif %}
from app.common.enums import QueueEnum
from app.core.base_schema import BaseSchema, UserBySchema from app.core.base_schema import BaseSchema, UserBySchema
class {{ class_name }}CreateSchema(BaseModel): class {{ class_name }}CreateSchema(BaseModel):
@@ -69,18 +70,28 @@ class {{ class_name }}QueryParam:
{% for column in columns %} {% for column in columns %}
{% if column.query_type == 'LIKE' %} {% if column.query_type == 'LIKE' %}
# 模糊查询字段 # 模糊查询字段
self.{{ column.column_name }} = ("like", {{ column.column_name }}) self.{{ column.column_name }} = (QueueEnum.like.value, {{ column.column_name }})
{% elif column.query_type == 'EQ' and column.column_name not in ['created_time', 'updated_time'] %} {% elif column.query_type == 'EQ' and column.column_name not in ['created_time', 'updated_time'] %}
# 精确查询字段 # 精确查询字段
self.{{ column.column_name }} = {{ column.column_name }} if {{ column.column_name }}:
self.{{ column.column_name }} = (QueueEnum.eq.value, {{ column.column_name }})
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% if table.created_time %} {% if table.created_time %}
# 时间范围查询 # 时间范围查询
if created_time and len(created_time) == 2: if created_time and len(created_time) == 2:
self.created_time = ("between", (created_time[0], created_time[1])) self.created_time = (QueueEnum.between.value, (created_time[0], created_time[1]))
{% endif %} {% endif %}
{% if table.updated_time %} {% if table.updated_time %}
if updated_time and len(updated_time) == 2: if updated_time and len(updated_time) == 2:
self.updated_time = ("between", (updated_time[0], updated_time[1])) self.updated_time = (QueueEnum.between.value, (updated_time[0], updated_time[1]))
{% endif %}
{% if table.created_id %}
# 关联查询字段
if created_id:
self.created_id = (QueueEnum.eq.value, created_id)
{% endif %}
{% if table.updated_id %}
if updated_id:
self.updated_id = (QueueEnum.eq.value, updated_id)
{% endif %} {% endif %}
@@ -1,16 +1,22 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import io import io
from fastapi import UploadFile
import pandas as pd import pandas as pd
from fastapi import UploadFile
from app.api.v1.module_system.auth.schema import AuthSchema
from app.core.base_schema import BatchSetAvailable from app.core.base_schema import BatchSetAvailable
from app.core.exceptions import CustomException from app.core.exceptions import CustomException
from app.utils.excel_util import ExcelUtil
from app.core.logger import log from app.core.logger import log
from app.api.v1.module_system.auth.schema import AuthSchema from app.utils.excel_util import ExcelUtil
from .schema import {{ class_name }}CreateSchema, {{ class_name }}UpdateSchema, {{ class_name }}OutSchema, {{ class_name }}QueryParam
from .crud import {{ class_name }}CRUD from .crud import {{ class_name }}CRUD
from .schema import (
{{ class_name }}CreateSchema,
{{ class_name }}UpdateSchema,
{{ class_name }}OutSchema,
{{ class_name }}QueryParam
)
class {{ class_name }}Service: class {{ class_name }}Service:
@@ -20,7 +26,16 @@ class {{ class_name }}Service:
@classmethod @classmethod
async def detail_{{ business_name }}_service(cls, auth: AuthSchema, id: int) -> dict: async def detail_{{ business_name }}_service(cls, auth: AuthSchema, id: int) -> dict:
"""详情""" """
详情
参数:
- auth: AuthSchema - 认证信息
- id: int - 数据ID
返回:
- dict - 数据详情
"""
obj = await {{ class_name }}CRUD(auth).get_by_id_{{ business_name }}_crud(id=id) obj = await {{ class_name }}CRUD(auth).get_by_id_{{ business_name }}_crud(id=id)
if not obj: if not obj:
raise CustomException(msg="该数据不存在") raise CustomException(msg="该数据不存在")
@@ -28,14 +43,36 @@ class {{ class_name }}Service:
@classmethod @classmethod
async def list_{{ business_name }}_service(cls, auth: AuthSchema, search: {{ class_name }}QueryParam | None = None, order_by: list[dict] | None = None) -> list[dict]: async def list_{{ business_name }}_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 search_dict = search.__dict__ if search else None
obj_list = await {{ class_name }}CRUD(auth).list_{{ business_name }}_crud(search=search_dict, order_by=order_by) obj_list = await {{ class_name }}CRUD(auth).list_{{ business_name }}_crud(search=search_dict, order_by=order_by)
return [{{ class_name }}OutSchema.model_validate(obj).model_dump() for obj in obj_list] return [{{ class_name }}OutSchema.model_validate(obj).model_dump() for obj in obj_list]
@classmethod @classmethod
async def page_{{ business_name }}_service(cls, auth: AuthSchema, page_no: int, page_size: int, search: {{ class_name }}QueryParam | None = None, order_by: list[dict] | None = None) -> dict: async def page_{{ business_name }}_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 {} search_dict = search.__dict__ if search else {}
order_by_list = order_by or [{'id': 'asc'}] order_by_list = order_by or [{'id': 'asc'}]
offset = (page_no - 1) * page_size offset = (page_no - 1) * page_size
@@ -49,8 +86,16 @@ class {{ class_name }}Service:
@classmethod @classmethod
async def create_{{ business_name }}_service(cls, auth: AuthSchema, data: {{ class_name }}CreateSchema) -> dict: async def create_{{ business_name }}_service(cls, auth: AuthSchema, data: {{ class_name }}CreateSchema) -> dict:
"""创建""" """
# 检查唯一性约束 创建
参数:
- auth: AuthSchema - 认证信息
- data: {{ class_name }}CreateSchema - 创建数据
返回:
- dict - 创建结果
"""
{% for column in columns %} {% for column in columns %}
{% if column.is_unique == '1' %} {% if column.is_unique == '1' %}
obj = await {{ class_name }}CRUD(auth).get({{ column.column_name }}=data.{{ column.column_name }}) obj = await {{ class_name }}CRUD(auth).get({{ column.column_name }}=data.{{ column.column_name }})
@@ -63,7 +108,17 @@ class {{ class_name }}Service:
@classmethod @classmethod
async def update_{{ business_name }}_service(cls, auth: AuthSchema, id: int, data: {{ class_name }}UpdateSchema) -> dict: async def update_{{ business_name }}_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 }}_crud(id=id) obj = await {{ class_name }}CRUD(auth).get_by_id_{{ business_name }}_crud(id=id)
if not obj: if not obj:
@@ -83,7 +138,16 @@ class {{ class_name }}Service:
@classmethod @classmethod
async def delete_{{ business_name }}_service(cls, auth: AuthSchema, ids: list[int]) -> None: async def delete_{{ business_name }}_service(cls, auth: AuthSchema, ids: list[int]) -> None:
"""删除""" """
删除
参数:
- auth: AuthSchema - 认证信息
- ids: list[int] - 数据ID列表
返回:
- None
"""
if len(ids) < 1: if len(ids) < 1:
raise CustomException(msg='删除失败,删除对象不能为空') raise CustomException(msg='删除失败,删除对象不能为空')
for id in ids: for id in ids:
@@ -94,36 +158,61 @@ class {{ class_name }}Service:
@classmethod @classmethod
async def set_available_{{ business_name }}_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None: async def set_available_{{ business_name }}_service(cls, auth: AuthSchema, data: BatchSetAvailable) -> None:
"""批量设置状态""" """
批量设置状态
参数:
- auth: AuthSchema - 认证信息
- data: BatchSetAvailable - 批量设置状态数据
返回:
- None
"""
await {{ class_name }}CRUD(auth).set_available_{{ business_name }}_crud(ids=data.ids, status=data.status) await {{ class_name }}CRUD(auth).set_available_{{ business_name }}_crud(ids=data.ids, status=data.status)
@classmethod @classmethod
async def batch_export_{{ business_name }}_service(cls, obj_list: list[dict]) -> bytes: async def batch_export_{{ business_name }}_service(cls, obj_list: list[dict]) -> bytes:
"""批量导出""" """
批量导出
参数:
- obj_list: list[dict] - 数据列表
返回:
- bytes - 导出的Excel文件内容
"""
mapping_dict = { mapping_dict = {
{% for column in columns %} {% for column in columns %}
'{{ column.column_name }}': '{{ column.column_comment }}', '{{ column.column_name }}': '{{ column.column_comment }}',
{% endfor %} {% endfor %}
'updated_id': '更新者ID',
} }
# 复制数据并转换状态
data = obj_list.copy() data = obj_list.copy()
for item in data: for item in data:
# 状态转换 # 处理状态
if 'status' in item: item["status"] = "启用" if item.get("status") == "0" else "停用"
item['status'] = '启用' if item.get('status') == '0' else '停用' # 处理创建者
# 创建者转换 creator_info = item.get("created_id")
creator_info = item.get('creator')
if isinstance(creator_info, dict): if isinstance(creator_info, dict):
item['creator'] = creator_info.get('name', '未知') item["created_id"] = creator_info.get("name", "未知")
elif creator_info is None: else:
item['creator'] = '未知' item["created_id"] = "未知"
return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict) return ExcelUtil.export_list2excel(list_data=data, mapping_dict=mapping_dict)
@classmethod @classmethod
async def batch_import_{{ business_name }}_service(cls, auth: AuthSchema, file: UploadFile, update_support: bool = False) -> str: async def batch_import_{{ business_name }}_service(cls, auth: AuthSchema, file: UploadFile, update_support: bool = False) -> str:
"""批量导入""" """
批量导入
参数:
- auth: AuthSchema - 认证信息
- file: UploadFile - 上传的Excel文件
- update_support: bool - 是否支持更新存在数据
返回:
- str - 导入结果信息
"""
header_dict = { header_dict = {
{% for column in columns %} {% for column in columns %}
'{{ column.column_comment }}': '{{ column.column_name }}', '{{ column.column_comment }}': '{{ column.column_name }}',
@@ -131,17 +220,20 @@ class {{ class_name }}Service:
} }
try: try:
# 读取Excel文件
contents = await file.read() contents = await file.read()
df = pd.read_excel(io.BytesIO(contents)) df = pd.read_excel(io.BytesIO(contents))
await file.close() await file.close()
if df.empty: if df.empty:
raise CustomException(msg="导入文件为空") raise CustomException(msg="导入文件为空")
# 检查表头是否完整
missing_headers = [header for header in header_dict.keys() if header not in df.columns] missing_headers = [header for header in header_dict.keys() if header not in df.columns]
if missing_headers: if missing_headers:
raise CustomException(msg=f"导入文件缺少必要的列: {', '.join(missing_headers)}") raise CustomException(msg=f"导入文件缺少必要的列: {', '.join(missing_headers)}")
# 重命名列名
df.rename(columns=header_dict, inplace=True) df.rename(columns=header_dict, inplace=True)
# 验证必填字段 # 验证必填字段
@@ -162,7 +254,7 @@ class {{ class_name }}Service:
success_count = 0 success_count = 0
count = 0 count = 0
for index, row in df.iterrows(): for _index, row in df.iterrows():
count += 1 count += 1
try: try:
data = { data = {
@@ -204,7 +296,12 @@ class {{ class_name }}Service:
@classmethod @classmethod
async def import_template_download_{{ business_name }}_service(cls) -> bytes: async def import_template_download_{{ business_name }}_service(cls) -> bytes:
"""下载导入模板""" """
下载导入模板
返回:
- bytes - Excel文件的二进制数据
"""
header_list = [ header_list = [
{% for column in columns %} {% for column in columns %}
'{{ column.column_comment }}', '{{ column.column_comment }}',
@@ -213,7 +310,6 @@ class {{ class_name }}Service:
selector_header_list = [] selector_header_list = []
option_list = [] option_list = []
# 添加下拉选项
{% for column in columns %} {% for column in columns %}
{% if column.html_type == 'select' and column.dict_type %} {% if column.html_type == 'select' and column.dict_type %}
selector_header_list.append('{{ column.column_comment }}') selector_header_list.append('{{ column.column_comment }}')
@@ -1,121 +1,6 @@
<!-- {{ function_name }} --> <!-- {{ function_name }} -->
<template> <template>
<div class="app-container"> <div class="app-container">
<!-- 搜索区域 -->
<div v-show="visible" class="search-container">
<el-form
ref="queryFormRef"
:model="queryFormData"
label-suffix=":"
:inline="true"
@submit.prevent="handleQuery"
>
{% for column in columns %}
{% if column.is_query == 1 %}
{% set dict_type = column.dict_type %}
{% 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" %}
<el-form-item prop="status" label="状态">
<el-select
v-model="queryFormData.status"
placeholder="请选择状态"
style="width: 170px"
clearable
>
<el-option value="0" label="启用" />
<el-option value="1" label="停用" />
</el-select>
</el-form-item>
{% elif column.column_name == "created_id"%}
<el-form-item v-if="isExpand" prop="created_id" label="创建人">
<UserTableSelect
v-model="queryFormData.created_id"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
{% elif column.column_name == "updated_id"%}
<el-form-item v-if="isExpand" prop="updated_id" label="更新人">
<UserTableSelect
v-model="queryFormData.updated_id"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
{% elif column.column_name == "created_time"%}
<el-form-item v-if="isExpand" prop="created_time" label="创建时间">
<DatePicker
v-model="createdDateRange"
@update:model-value="handleCreatedDateRangeChange"
/>
</el-form-item>
{% elif column.column_name == "updated_time"%}
<el-form-item v-if="isExpand" prop="updated_time" label="更新时间">
<DatePicker
v-model="updatedDateRange"
@update:model-value="handleUpdatedDateRangeChange"
/>
</el-form-item>
{% elif column.html_type == "input" %}
<el-form-item label="{{ comment }}" prop="{{ column.column_name }}">
<el-input v-model="queryFormData.{{ column.column_name }}" placeholder="请输入{{ comment }}" clearable />
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and dict_type != "" %}
<el-form-item label="{{ comment }}" prop="{{ column.column_name }}">
<el-select v-model="queryFormData.{{ column.column_name }}" placeholder="请选择{{ comment }}" style="width: 180px" clearable>
<el-option v-for="dict in dictStore.getDictArray('{{ dict_type }}')" :key="dict.dict_value" :label="dict.dict_label" :value="dict.dict_value" />
</el-select>
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and dict_type %}
<el-form-item label="{{ comment }}" prop="{{ column.column_name }}">
<el-select v-model="queryFormData.{{ column.column_name }}" placeholder="请选择{{ comment }}" clearable>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.html_type == "datetime" and column.query_type != "BETWEEN" %}
<el-form-item label="{{ comment }}" prop="{{ column.column_name }}">
<el-date-picker v-model="queryFormData.{{ column.column_name }}" type="date" value-format="YYYY-MM-DD" clearable placeholder="请选择{{ comment }}" />
</el-form-item>
{% endif %}
{% endif %}
{% endfor %}
<!-- 查询、重置、展开/收起按钮 -->
<el-form-item>
<el-button
v-hasPerm="['{{ module_name }}:{{ business_name }}:query']"
type="primary"
icon="search"
@click="handleQuery"
>
查询
</el-button>
<el-button
v-hasPerm="['{{ module_name }}:{{ business_name }}:query']"
icon="refresh"
@click="handleResetQuery"
>
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
{{ '{{' }} isExpand ? "收起" : "展开" {{ '}}' }}
<el-icon>
<template v-if="isExpand">
<ArrowUp />
</template>
<template v-else>
<ArrowDown />
</template>
</el-icon>
</el-link>
</template>
</el-form-item>
</el-form>
</div>
<!-- 内容区域 --> <!-- 内容区域 -->
<el-card class="data-table"> <el-card class="data-table">
<template #header> <template #header>
@@ -127,6 +12,126 @@
</el-tooltip> </el-tooltip>
</span> </span>
</div> </div>
<!-- 搜索区域 -->
<div v-show="visible" class="search-container">
<el-form
ref="queryFormRef"
:model="queryFormData"
label-suffix=":"
:inline="true"
@submit.prevent="handleQuery"
>
{% for column in columns %}
{% if column.is_query == 1 %}
{% set dict_type = column.dict_type %}
{% 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" %}
<el-form-item prop="status" label="状态">
<el-select
v-model="queryFormData.status"
placeholder="请选择状态"
style="width: 170px"
clearable
>
<el-option value="0" label="启用" />
<el-option value="1" label="停用" />
</el-select>
</el-form-item>
{% elif column.column_name == "created_id"%}
<el-form-item v-if="isExpand" prop="created_id" label="创建人">
<UserTableSelect
v-model="queryFormData.created_id"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
{% elif column.column_name == "updated_id"%}
<el-form-item v-if="isExpand" prop="updated_id" label="更新人">
<UserTableSelect
v-model="queryFormData.updated_id"
@confirm-click="handleConfirm"
@clear-click="handleQuery"
/>
</el-form-item>
{% elif column.column_name == "created_time"%}
<el-form-item v-if="isExpand" prop="created_time" label="创建时间">
<DatePicker
v-model="createdDateRange"
@update:model-value="handleCreatedDateRangeChange"
/>
</el-form-item>
{% elif column.column_name == "updated_time"%}
<el-form-item v-if="isExpand" prop="updated_time" label="更新时间">
<DatePicker
v-model="updatedDateRange"
@update:model-value="handleUpdatedDateRangeChange"
/>
</el-form-item>
{% elif column.html_type == "input" %}
<el-form-item label="{{ comment }}" prop="{{ column.column_name }}">
<el-input v-model="queryFormData.{{ column.column_name }}" placeholder="请输入{{ comment }}" clearable />
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and dict_type != "" %}
<el-form-item label="{{ comment }}" prop="{{ column.column_name }}">
<el-select v-model="queryFormData.{{ column.column_name }}" placeholder="请选择{{ comment }}" style="width: 180px" clearable>
<el-option v-for="dict in dictStore.getDictArray('{{ dict_type }}')" :key="dict.dict_value" :label="dict.dict_label" :value="dict.dict_value" />
</el-select>
</el-form-item>
{% elif (column.html_type == "select" or column.html_type == "radio") and dict_type %}
<el-form-item label="{{ comment }}" prop="{{ column.column_name }}">
<el-select v-model="queryFormData.{{ column.column_name }}" placeholder="请选择{{ comment }}" clearable>
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
{% elif column.html_type == "datetime" and column.query_type != "BETWEEN" %}
<el-form-item label="{{ comment }}" prop="{{ column.column_name }}">
<el-date-picker v-model="queryFormData.{{ column.column_name }}" type="date" value-format="YYYY-MM-DD" clearable placeholder="请选择{{ comment }}" />
</el-form-item>
{% endif %}
{% endif %}
{% endfor %}
<!-- 查询、重置、展开/收起按钮 -->
<el-form-item>
<el-button
v-hasPerm="['{{ module_name }}:{{ business_name }}:query']"
type="primary"
icon="search"
@click="handleQuery"
>
查询
</el-button>
<el-button
v-hasPerm="['{{ module_name }}:{{ business_name }}:query']"
icon="refresh"
@click="handleResetQuery"
>
重置
</el-button>
<!-- 展开/收起 -->
<template v-if="isExpandable">
<el-link
class="ml-3"
type="primary"
underline="never"
@click="isExpand = !isExpand"
>
{{ '{{' }} isExpand ? "收起" : "展开" {{ '}}' }}
<el-icon>
<template v-if="isExpand">
<ArrowUp />
</template>
<template v-else>
<ArrowDown />
</template>
</el-icon>
</el-link>
</template>
</el-form-item>
</el-form>
</div>
</template> </template>
<!-- 功能区域 --> <!-- 功能区域 -->
@@ -242,7 +247,6 @@
:data="pageTableData" :data="pageTableData"
highlight-current-row highlight-current-row
class="data-table__content" class="data-table__content"
:height="450"
border border
stripe stripe
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
@@ -277,6 +281,7 @@
label="{{ comment }}" label="{{ comment }}"
prop="{{ python_field }}" prop="{{ python_field }}"
min-width="140" min-width="140"
show-overflow-tooltip
/> />
{% if python_field in ['status', 'created_id', 'updated_id'] %} {% if python_field in ['status', 'created_id', 'updated_id'] %}
<el-table-column <el-table-column
@@ -284,6 +289,7 @@
label="{{ comment }}" label="{{ comment }}"
prop="{{ python_field }}" prop="{{ python_field }}"
min-width="140" min-width="140"
show-overflow-tooltip
> >
{% if python_field == "status" %} {% if python_field == "status" %}
<template #default="scope"> <template #default="scope">
@@ -488,6 +494,7 @@
<ImportModal <ImportModal
v-model="importDialogVisible" v-model="importDialogVisible"
:content-config="curdContentConfig" :content-config="curdContentConfig"
:loading="uploadLoading"
@upload="handleUpload" @upload="handleUpload"
/> />
@@ -525,24 +532,14 @@ import {{ class_name }}API, {
} from "@/api/{{ module_name }}/{{ business_name }}"; } from "@/api/{{ module_name }}/{{ business_name }}";
const visible = ref(true); const visible = ref(true);
const isExpand = ref(false);
const isExpandable = ref(true);
const queryFormRef = ref(); const queryFormRef = ref();
const dataFormRef = ref(); const dataFormRef = ref();
const total = ref(0); const total = ref(0);
const selectIds = ref<number[]>([]); const selectIds = ref<number[]>([]);
const selectionRows = ref<{{ class_name }}Table[]>([]); const selectionRows = ref<{{ class_name }}Table[]>([]);
const loading = ref(false); const loading = ref(false);
const isExpand = ref(false);
// 字典仓库与需要加载的字典类型 const isExpandable = ref(true);
const dictStore = useDictStore();
const dictTypes: any = [
{% for column in columns %}
{% if column.dict_type %}
"{{ column.dict_type }}",
{% endif %}
{% endfor %}
];
// 分页表单 // 分页表单
const pageTableData = ref<{{ class_name }}Table[]>([]); const pageTableData = ref<{{ class_name }}Table[]>([]);
@@ -640,6 +637,16 @@ const formData = reactive<{{ class_name }}Form>({
{% endfor %} {% endfor %}
}); });
// 字典仓库与需要加载的字典类型
const dictStore = useDictStore();
const dictTypes: any = [
{% for column in columns %}
{% if column.dict_type %}
"{{ column.dict_type }}",
{% endif %}
{% endfor %}
];
// 弹窗状态 // 弹窗状态
const dialogVisible = reactive({ const dialogVisible = reactive({
title: "", title: "",
@@ -659,6 +666,7 @@ const rules = reactive({
// 导入弹窗显示状态 // 导入弹窗显示状态
const importDialogVisible = ref(false); const importDialogVisible = ref(false);
const uploadLoading = ref(false);
// 导出弹窗显示状态 // 导出弹窗显示状态
const exportsDialogVisible = ref(false); const exportsDialogVisible = ref(false);
@@ -780,10 +788,11 @@ async function handleSubmit() {
if (valid) { if (valid) {
loading.value = true; loading.value = true;
// 根据弹窗传入的参数(deatil\create\update)判断走什么逻辑 // 根据弹窗传入的参数(deatil\create\update)判断走什么逻辑
const submitData = { ...formData };
const id = formData.id; const id = formData.id;
if (id) { if (id) {
try { try {
await {{ class_name }}API.update{{ class_name }}(id, { id, ...formData }); await {{ class_name }}API.update{{ class_name }}(id, { id, ...submitData });
dialogVisible.visible = false; dialogVisible.visible = false;
resetForm(); resetForm();
handleCloseDialog(); handleCloseDialog();
@@ -795,7 +804,7 @@ async function handleSubmit() {
} }
} else { } else {
try { try {
await {{ class_name }}API.create{{ class_name }}(formData); await {{ class_name }}API.create{{ class_name }}(submitData);
dialogVisible.visible = false; dialogVisible.visible = false;
resetForm(); resetForm();
handleCloseDialog(); handleCloseDialog();
@@ -861,6 +870,7 @@ async function handleMoreClick(status: string) {
// 处理上传 // 处理上传
const handleUpload = async (formData: FormData) => { const handleUpload = async (formData: FormData) => {
try { try {
uploadLoading.value = true;
const response = await {{ class_name }}API.import{{ class_name }}(formData); const response = await {{ class_name }}API.import{{ class_name }}(formData);
if (response.data.code === ResultEnum.SUCCESS) { if (response.data.code === ResultEnum.SUCCESS) {
ElMessage.success(`${response.data.msg}${response.data.data}`); ElMessage.success(`${response.data.msg}${response.data.data}`);
@@ -869,6 +879,8 @@ const handleUpload = async (formData: FormData) => {
} }
} catch (error: any) { } catch (error: any) {
console.error(error); console.error(error);
} finally {
uploadLoading.value = false;
} }
}; };
+10
View File
@@ -43,3 +43,13 @@ $border: 1px solid var(--el-border-color-light);
gap: 8px; gap: 8px;
justify-content: flex-end; justify-content: flex-end;
} }
// 列表
.el-card {
.el-card__body {
display: flex;
flex: 1;
flex-direction: column;
overflow: hidden;
}
}
+6
View File
@@ -98,6 +98,9 @@ html.sidebar-color-blue .layout-mix .layout__sidebar--left .el-menu {
// 表格区域样式 // 表格区域样式
.data-table { .data-table {
display: flex;
flex-direction: column;
height: 100%;
margin-bottom: 4px; margin-bottom: 4px;
// 表格工具栏区域 // 表格工具栏区域
@@ -115,6 +118,9 @@ html.sidebar-color-blue .layout-mix .layout__sidebar--left .el-menu {
// 表格内容区域 // 表格内容区域
&__content { &__content {
flex: 1;
height: auto !important;
max-height: none !important;
margin: 0px 0; // 表格内容区域添加内边距 margin: 0px 0; // 表格内容区域添加内边距
} }
@@ -246,18 +246,21 @@
label="名称" label="名称"
prop="name" prop="name"
min-width="140" min-width="140"
show-overflow-tooltip
/> />
<el-table-column <el-table-column
v-if="tableColumns.find((col) => col.prop === 'uuid')?.show" v-if="tableColumns.find((col) => col.prop === 'uuid')?.show"
label="UUID" label="UUID"
prop="uuid" prop="uuid"
min-width="180" min-width="180"
show-overflow-tooltip
/> />
<el-table-column <el-table-column
v-if="tableColumns.find((col) => col.prop === 'status')?.show" v-if="tableColumns.find((col) => col.prop === 'status')?.show"
label="状态" label="状态"
prop="status" prop="status"
min-width="120" min-width="120"
show-overflow-tooltip
> >
<template #default="scope"> <template #default="scope">
<el-tag :type="scope.row.status ? 'success' : 'info'"> <el-tag :type="scope.row.status ? 'success' : 'info'">
@@ -270,24 +273,28 @@
label="整数" label="整数"
prop="a" prop="a"
min-width="100" min-width="100"
show-overflow-tooltip
/> />
<el-table-column <el-table-column
v-if="tableColumns.find((col) => col.prop === 'b')?.show" v-if="tableColumns.find((col) => col.prop === 'b')?.show"
label="大整数" label="大整数"
prop="b" prop="b"
min-width="120" min-width="120"
show-overflow-tooltip
/> />
<el-table-column <el-table-column
v-if="tableColumns.find((col) => col.prop === 'c')?.show" v-if="tableColumns.find((col) => col.prop === 'c')?.show"
label="浮点数" label="浮点数"
prop="c" prop="c"
min-width="100" min-width="100"
show-overflow-tooltip
/> />
<el-table-column <el-table-column
v-if="tableColumns.find((col) => col.prop === 'd')?.show" v-if="tableColumns.find((col) => col.prop === 'd')?.show"
label="布尔值" label="布尔值"
prop="d" prop="d"
min-width="100" min-width="100"
show-overflow-tooltip
> >
<template #default="scope"> <template #default="scope">
<el-tag :type="scope.row.d ? 'success' : 'danger'"> <el-tag :type="scope.row.d ? 'success' : 'danger'">
@@ -300,58 +307,56 @@
label="日期" label="日期"
prop="e" prop="e"
min-width="120" min-width="120"
show-overflow-tooltip
/> />
<el-table-column <el-table-column
v-if="tableColumns.find((col) => col.prop === 'f')?.show" v-if="tableColumns.find((col) => col.prop === 'f')?.show"
label="时间" label="时间"
prop="f" prop="f"
min-width="120" min-width="120"
show-overflow-tooltip
/> />
<el-table-column <el-table-column
v-if="tableColumns.find((col) => col.prop === 'g')?.show" v-if="tableColumns.find((col) => col.prop === 'g')?.show"
label="日期时间" label="日期时间"
prop="g" prop="g"
min-width="180" min-width="180"
show-overflow-tooltip
/> />
<el-table-column <el-table-column
v-if="tableColumns.find((col) => col.prop === 'h')?.show" v-if="tableColumns.find((col) => col.prop === 'h')?.show"
label="长文本" label="长文本"
prop="h" prop="h"
min-width="140" min-width="140"
show-overflow-tooltip
/> />
<el-table-column
v-if="tableColumns.find((col) => col.prop === 'i')?.show"
label="元数据"
prop="i"
min-width="140"
>
<template #default="scope">
<JsonPretty :value="scope.row.i" height="100px" />
</template>
</el-table-column>
<el-table-column <el-table-column
v-if="tableColumns.find((col) => col.prop === 'description')?.show" v-if="tableColumns.find((col) => col.prop === 'description')?.show"
label="描述" label="描述"
prop="description" prop="description"
min-width="140" min-width="140"
show-overflow-tooltip
/> />
<el-table-column <el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_time')?.show" v-if="tableColumns.find((col) => col.prop === 'created_time')?.show"
label="创建时间" label="创建时间"
prop="created_time" prop="created_time"
min-width="180" min-width="180"
show-overflow-tooltip
/> />
<el-table-column <el-table-column
v-if="tableColumns.find((col) => col.prop === 'updated_time')?.show" v-if="tableColumns.find((col) => col.prop === 'updated_time')?.show"
label="更新时间" label="更新时间"
prop="updated_time" prop="updated_time"
min-width="180" min-width="180"
show-overflow-tooltip
/> />
<el-table-column <el-table-column
v-if="tableColumns.find((col) => col.prop === 'created_id')?.show" v-if="tableColumns.find((col) => col.prop === 'created_id')?.show"
label="创建人" label="创建人"
prop="created_id" prop="created_id"
min-width="120" min-width="120"
show-overflow-tooltip
> >
<template #default="scope"> <template #default="scope">
<el-tag>{{ scope.row.created_by?.name }}</el-tag> <el-tag>{{ scope.row.created_by?.name }}</el-tag>
@@ -362,6 +367,7 @@
label="更新人" label="更新人"
prop="updated_id" prop="updated_id"
min-width="120" min-width="120"
show-overflow-tooltip
> >
<template #default="scope"> <template #default="scope">
<el-tag>{{ scope.row.updated_by?.name }}</el-tag> <el-tag>{{ scope.row.updated_by?.name }}</el-tag>
@@ -372,7 +378,7 @@
fixed="right" fixed="right"
label="操作" label="操作"
align="center" align="center"
min-width="180" min-width="200"
> >
<template #default="scope"> <template #default="scope">
<el-button <el-button
@@ -436,8 +442,8 @@
{{ detailFormData.uuid }} {{ detailFormData.uuid }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="状态" :span="2"> <el-descriptions-item label="状态" :span="2">
<el-tag :type="detailFormData.status ? 'success' : 'danger'"> <el-tag :type="detailFormData.status == '0' ? 'success' : 'danger'">
{{ detailFormData.status ? "启用" : "停用" }} {{ detailFormData.status == "0" ? "启用" : "停用" }}
</el-tag> </el-tag>
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="整数" :span="2"> <el-descriptions-item label="整数" :span="2">
@@ -871,8 +877,6 @@ async function handleCloseDialog() {
// 打开弹窗 // 打开弹窗
async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) { async function handleOpenDialog(type: "create" | "update" | "detail", id?: number) {
// 每次打开弹窗前先重置表单
resetForm();
dialogVisible.type = type; dialogVisible.type = type;
if (id) { if (id) {
const response = await DemoAPI.getDemoDetail(id); const response = await DemoAPI.getDemoDetail(id);