mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-21 12:52:26 +00:00
refactor(common): 替换分页服务方法及代码生成模块重构
- 将分页服务中方法名由get_page_obj统一替换为paginate - 注意相关controller均调整调用方式,保证统一接口调用 - 代码生成模块数据库模型统一替换为GenTableModel和GenTableColumnModel - 更改数据库类型及分页相关配置为settings.DATABASE_TYPE统一管理 - 重构代码生成模块查询参数,新增GenTableQueryParam和GenTableColumnQueryParam类支持更灵活查询 - 数据模型中Pydantic Schema类型统一调整为Schema后缀 - 优化异常处理,增加请求参数验证错误的友好提示映射 - 调整中间件及依赖以支持更严格的类型检查及更健壮的用户权限认证逻辑 - 微调日志打印格式,改进请求日志信息输出风格
This commit is contained in:
@@ -37,7 +37,7 @@ async def get_obj_list_controller(
|
|||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["application:myapp:query"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["application:myapp:query"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await ApplicationService.get_application_list_service(auth=auth, search=search, order_by=page.order_by)
|
result_dict_list = await ApplicationService.get_application_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
||||||
logger.info(f"查询应用列表成功")
|
logger.info(f"查询应用列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询应用列表成功")
|
return SuccessResponse(data=result_dict, msg="查询应用列表成功")
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ async def get_obj_list_controller(
|
|||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["demo:example:query"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["demo:example:query"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await DemoService.get_demo_list_service(auth=auth, search=search, order_by=page.order_by)
|
result_dict_list = await DemoService.get_demo_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
||||||
logger.info(f"查询示例列表成功")
|
logger.info(f"查询示例列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询公告列表成功")
|
return SuccessResponse(data=result_dict, msg="查询公告列表成功")
|
||||||
|
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ from sqlalchemy.orm import selectinload
|
|||||||
from sqlglot.expressions import Expression
|
from sqlglot.expressions import Expression
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from .model import GenTable, GenTableColumn
|
from .model import GenTableModel, GenTableColumnModel
|
||||||
from config.env import DataBaseConfig
|
from app.config.setting import settings
|
||||||
from utils.page_util import PageUtil
|
from app.common.request import PaginationService
|
||||||
from .schema import (
|
from .schema import (
|
||||||
GenTableBaseModel,
|
GenTableBaseSchema,
|
||||||
GenTableColumnBaseModel,
|
GenTableColumnBaseSchema,
|
||||||
GenTableColumnModel,
|
GenTableColumnSchema,
|
||||||
GenTableModel,
|
GenTableSchema,
|
||||||
GenTablePageQueryModel,
|
|
||||||
)
|
)
|
||||||
|
from .param import GenTableQueryParam, GenTableColumnBaseSchema
|
||||||
|
|
||||||
|
|
||||||
class GenTableDao:
|
class GenTableDao:
|
||||||
@@ -36,7 +36,7 @@ class GenTableDao:
|
|||||||
gen_table_info = (
|
gen_table_info = (
|
||||||
(
|
(
|
||||||
await db.execute(
|
await db.execute(
|
||||||
select(GenTable).options(selectinload(GenTable.columns)).where(GenTable.table_id == table_id)
|
select(GenTableModel).options(selectinload(GenTableModel.columns)).where(GenTableModel.table_id == table_id)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.scalars()
|
.scalars()
|
||||||
@@ -57,7 +57,7 @@ class GenTableDao:
|
|||||||
gen_table_info = (
|
gen_table_info = (
|
||||||
(
|
(
|
||||||
await db.execute(
|
await db.execute(
|
||||||
select(GenTable).options(selectinload(GenTable.columns)).where(GenTable.table_name == table_name)
|
select(GenTableModel).options(selectinload(GenTableModel.columns)).where(GenTableModel.table_name == table_name)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.scalars()
|
.scalars()
|
||||||
@@ -74,7 +74,7 @@ class GenTableDao:
|
|||||||
:param db: orm对象
|
:param db: orm对象
|
||||||
:return: 所有业务表信息
|
:return: 所有业务表信息
|
||||||
"""
|
"""
|
||||||
gen_table_all = (await db.execute(select(GenTable).options(selectinload(GenTable.columns)))).scalars().all()
|
gen_table_all = (await db.execute(select(GenTableModel).options(selectinload(GenTableModel.columns)))).scalars().all()
|
||||||
|
|
||||||
return gen_table_all
|
return gen_table_all
|
||||||
|
|
||||||
@@ -88,11 +88,11 @@ class GenTableDao:
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
for sql_statement in sql_statements:
|
for sql_statement in sql_statements:
|
||||||
sql = sql_statement.sql(dialect=DataBaseConfig.sqlglot_parse_dialect)
|
sql = sql_statement.sql(dialect=settings.DATABASE_TYPE)
|
||||||
await db.execute(text(sql))
|
await db.execute(text(sql))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_gen_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False):
|
async def get_gen_table_list(cls, db: AsyncSession, query_object: GenTableQueryParam, is_page: bool = False):
|
||||||
"""
|
"""
|
||||||
根据查询参数获取代码生成业务表列表信息
|
根据查询参数获取代码生成业务表列表信息
|
||||||
|
|
||||||
@@ -102,16 +102,16 @@ class GenTableDao:
|
|||||||
:return: 代码生成业务表列表信息对象
|
:return: 代码生成业务表列表信息对象
|
||||||
"""
|
"""
|
||||||
query = (
|
query = (
|
||||||
select(GenTable)
|
select(GenTableModel)
|
||||||
.options(selectinload(GenTable.columns))
|
.options(selectinload(GenTableModel.columns))
|
||||||
.where(
|
.where(
|
||||||
func.lower(GenTable.table_name).like(f'%{query_object.table_name.lower()}%')
|
func.lower(GenTableModel.table_name).like(f'%{query_object.table_name.lower()}%')
|
||||||
if query_object.table_name
|
if query_object.table_name
|
||||||
else True,
|
else True,
|
||||||
func.lower(GenTable.table_comment).like(f'%{query_object.table_comment.lower()}%')
|
func.lower(GenTableModel.table_comment).like(f'%{query_object.table_comment.lower()}%')
|
||||||
if query_object.table_comment
|
if query_object.table_comment
|
||||||
else True,
|
else True,
|
||||||
GenTable.create_time.between(
|
GenTableModel.create_time.between(
|
||||||
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
|
datetime.combine(datetime.strptime(query_object.begin_time, '%Y-%m-%d'), time(00, 00, 00)),
|
||||||
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
|
datetime.combine(datetime.strptime(query_object.end_time, '%Y-%m-%d'), time(23, 59, 59)),
|
||||||
)
|
)
|
||||||
@@ -120,12 +120,12 @@ class GenTableDao:
|
|||||||
)
|
)
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
gen_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
gen_table_list = await PaginationService.paginate(db, query, query_object.page_no, query_object.page_size, is_page)
|
||||||
|
|
||||||
return gen_table_list
|
return gen_table_list
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def get_gen_db_table_list(cls, db: AsyncSession, query_object: GenTablePageQueryModel, is_page: bool = False):
|
async def get_gen_db_table_list(cls, db: AsyncSession, query_object: GenTableQueryParam, is_page: bool = False):
|
||||||
"""
|
"""
|
||||||
根据查询参数获取数据库列表信息
|
根据查询参数获取数据库列表信息
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ class GenTableDao:
|
|||||||
:param is_page: 是否开启分页
|
:param is_page: 是否开启分页
|
||||||
:return: 数据库列表信息对象
|
:return: 数据库列表信息对象
|
||||||
"""
|
"""
|
||||||
if DataBaseConfig.db_type == 'postgresql':
|
if settings.DATABASE_TYPE == 'postgresql':
|
||||||
query_sql = """
|
query_sql = """
|
||||||
table_name as table_name,
|
table_name as table_name,
|
||||||
table_comment as table_comment,
|
table_comment as table_comment,
|
||||||
@@ -166,12 +166,12 @@ class GenTableDao:
|
|||||||
if query_object.table_comment:
|
if query_object.table_comment:
|
||||||
query_sql += """and lower(table_comment) like lower(concat('%', :table_comment, '%'))"""
|
query_sql += """and lower(table_comment) like lower(concat('%', :table_comment, '%'))"""
|
||||||
if query_object.begin_time:
|
if query_object.begin_time:
|
||||||
if DataBaseConfig.db_type == 'postgresql':
|
if settings.DATABASE_TYPE == 'postgresql':
|
||||||
query_sql += """and create_time::date >= to_date(:begin_time, 'yyyy-MM-dd')"""
|
query_sql += """and create_time::date >= to_date(:begin_time, 'yyyy-MM-dd')"""
|
||||||
else:
|
else:
|
||||||
query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:begin_time, '%Y%m%d')"""
|
query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:begin_time, '%Y%m%d')"""
|
||||||
if query_object.end_time:
|
if query_object.end_time:
|
||||||
if DataBaseConfig.db_type == 'postgresql':
|
if settings.DATABASE_TYPE == 'postgresql':
|
||||||
query_sql += """and create_time::date <= to_date(:end_time, 'yyyy-MM-dd')"""
|
query_sql += """and create_time::date <= to_date(:end_time, 'yyyy-MM-dd')"""
|
||||||
else:
|
else:
|
||||||
query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:end_time, '%Y%m%d')"""
|
query_sql += """and date_format(create_time, '%Y%m%d') >= date_format(:end_time, '%Y%m%d')"""
|
||||||
@@ -184,7 +184,7 @@ class GenTableDao:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
gen_db_table_list = await PageUtil.paginate(db, query, query_object.page_num, query_object.page_size, is_page)
|
gen_db_table_list = await PaginationService.paginate(db, query, query_object.page_no, query_object.page_size, is_page)
|
||||||
|
|
||||||
return gen_db_table_list
|
return gen_db_table_list
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ class GenTableDao:
|
|||||||
:param table_names: 业务表名称组
|
:param table_names: 业务表名称组
|
||||||
:return: 数据库列表信息对象
|
:return: 数据库列表信息对象
|
||||||
"""
|
"""
|
||||||
if DataBaseConfig.db_type == 'postgresql':
|
if settings.DATABASE_TYPE == 'postgresql':
|
||||||
query_sql = """
|
query_sql = """
|
||||||
select
|
select
|
||||||
table_name as table_name,
|
table_name as table_name,
|
||||||
@@ -240,7 +240,7 @@ class GenTableDao:
|
|||||||
:param gen_table: 业务表对象
|
:param gen_table: 业务表对象
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
db_gen_table = GenTable(**GenTableBaseModel(**gen_table.model_dump(by_alias=True)).model_dump())
|
db_gen_table = GenTableModel(**GenTableBaseSchema(**gen_table.model_dump(by_alias=True)).model_dump())
|
||||||
db.add(db_gen_table)
|
db.add(db_gen_table)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
|
|
||||||
@@ -255,7 +255,7 @@ class GenTableDao:
|
|||||||
:param gen_table: 需要更新的业务表字典
|
:param gen_table: 需要更新的业务表字典
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
await db.execute(update(GenTable), [GenTableBaseModel(**gen_table).model_dump()])
|
await db.execute(update(GenTableModel), [GenTableBaseSchema(**gen_table).model_dump()])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def delete_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel):
|
async def delete_gen_table_dao(cls, db: AsyncSession, gen_table: GenTableModel):
|
||||||
@@ -266,7 +266,7 @@ class GenTableDao:
|
|||||||
:param gen_table: 业务表对象
|
:param gen_table: 业务表对象
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
await db.execute(delete(GenTable).where(GenTable.table_id.in_([gen_table.table_id])))
|
await db.execute(delete(GenTableModel).where(GenTableModel.table_id.in_([gen_table.table_id])))
|
||||||
|
|
||||||
|
|
||||||
class GenTableColumnDao:
|
class GenTableColumnDao:
|
||||||
@@ -286,7 +286,7 @@ class GenTableColumnDao:
|
|||||||
gen_table_column_list = (
|
gen_table_column_list = (
|
||||||
(
|
(
|
||||||
await db.execute(
|
await db.execute(
|
||||||
select(GenTableColumn).where(GenTableColumn.table_id == table_id).order_by(GenTableColumn.sort)
|
select(GenTableColumnModel).where(GenTableColumnModel.table_id == table_id).order_by(GenTableColumnModel.sort)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.scalars()
|
.scalars()
|
||||||
@@ -304,7 +304,7 @@ class GenTableColumnDao:
|
|||||||
:param table_name: 业务表名称
|
:param table_name: 业务表名称
|
||||||
:return: 业务表字段列表信息对象
|
:return: 业务表字段列表信息对象
|
||||||
"""
|
"""
|
||||||
if DataBaseConfig.db_type == 'postgresql':
|
if settings.DATABASE_TYPE == 'postgresql':
|
||||||
query_sql = """
|
query_sql = """
|
||||||
select
|
select
|
||||||
column_name, is_required, is_pk, sort, column_comment, is_increment, column_type
|
column_name, is_required, is_pk, sort, column_comment, is_increment, column_type
|
||||||
@@ -354,8 +354,8 @@ class GenTableColumnDao:
|
|||||||
:param gen_table_column: 岗位对象
|
:param gen_table_column: 岗位对象
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
db_gen_table_column = GenTableColumn(
|
db_gen_table_column = GenTableColumnModel(
|
||||||
**GenTableColumnBaseModel(**gen_table_column.model_dump(by_alias=True)).model_dump()
|
**GenTableColumnBaseSchema(**gen_table_column.model_dump(by_alias=True)).model_dump()
|
||||||
)
|
)
|
||||||
db.add(db_gen_table_column)
|
db.add(db_gen_table_column)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
@@ -371,7 +371,7 @@ class GenTableColumnDao:
|
|||||||
:param gen_table_column: 需要更新的业务表字段字典
|
:param gen_table_column: 需要更新的业务表字段字典
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
await db.execute(update(GenTableColumn), [GenTableColumnBaseModel(**gen_table_column).model_dump()])
|
await db.execute(update(GenTableColumnModel), [GenTableColumnBaseSchema(**gen_table_column).model_dump()])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def delete_gen_table_column_by_table_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel):
|
async def delete_gen_table_column_by_table_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel):
|
||||||
@@ -382,7 +382,7 @@ class GenTableColumnDao:
|
|||||||
:param gen_table_column: 业务表字段对象
|
:param gen_table_column: 业务表字段对象
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
await db.execute(delete(GenTableColumn).where(GenTableColumn.table_id.in_([gen_table_column.table_id])))
|
await db.execute(delete(GenTableColumnModel).where(GenTableColumnModel.table_id.in_([gen_table_column.table_id])))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def delete_gen_table_column_by_column_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel):
|
async def delete_gen_table_column_by_column_id_dao(cls, db: AsyncSession, gen_table_column: GenTableColumnModel):
|
||||||
@@ -393,4 +393,4 @@ class GenTableColumnDao:
|
|||||||
:param post: 业务表字段对象
|
:param post: 业务表字段对象
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
await db.execute(delete(GenTableColumn).where(GenTableColumn.column_id.in_([gen_table_column.column_id])))
|
await db.execute(delete(GenTableColumnModel).where(GenTableColumnModel.column_id.in_([gen_table_column.column_id])))
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ class GenTableColumnModel(CreatorMixin):
|
|||||||
"""
|
"""
|
||||||
代码生成业务表字段
|
代码生成业务表字段
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__tablename__ = 'gen_table_column'
|
__tablename__ = 'gen_table_column'
|
||||||
__table_args__ = ({'comment': '代码生成业务表字段'})
|
__table_args__ = ({'comment': '代码生成业务表字段'})
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,64 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
@as_query
|
from datetime import datetime
|
||||||
class GenTablePageQuerySchema(GenTableQuerySchema):
|
from typing import Optional
|
||||||
"""
|
from fastapi import Query
|
||||||
代码生成业务表分页查询模型
|
|
||||||
"""
|
|
||||||
|
|
||||||
page_num: int = Field(default=1, description='当前页码')
|
from app.core.validator import DateTimeStr
|
||||||
page_size: int = Field(default=10, description='每页记录数')
|
from app.common.request import PageResultSchema
|
||||||
|
from .schema import GenTableBaseSchema, GenTableColumnBaseSchema
|
||||||
|
|
||||||
|
|
||||||
@as_query
|
class GenTableQueryParam(PageResultSchema, GenTableBaseSchema):
|
||||||
class GenTableColumnPageQuerySchema(GenTableColumnQuerySchema):
|
"""数据库表查询参数"""
|
||||||
"""
|
|
||||||
代码生成业务表字段分页查询模型
|
def __init__(
|
||||||
"""
|
self,
|
||||||
|
name: Optional[str] = Query(None, description="名称"),
|
||||||
|
status: Optional[bool] = Query(None, description="是否启用"),
|
||||||
|
creator: Optional[int] = Query(None, description="创建人"),
|
||||||
|
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
|
||||||
|
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
|
||||||
|
) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# 模糊查询字段
|
||||||
|
self.name = ("like", name)
|
||||||
|
|
||||||
|
# 精确查询字段
|
||||||
|
self.creator_id = creator
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
# 时间范围查询
|
||||||
|
if start_time and end_time:
|
||||||
|
start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S')
|
||||||
|
end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S')
|
||||||
|
self.created_at = ("between", (start_datetime, end_datetime))
|
||||||
|
|
||||||
|
|
||||||
|
class GenTableColumnQueryParam(PageResultSchema, GenTableColumnBaseSchema):
|
||||||
|
"""数据库表字段查询参数"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: Optional[str] = Query(None, description="名称"),
|
||||||
|
status: Optional[bool] = Query(None, description="是否启用"),
|
||||||
|
creator: Optional[int] = Query(None, description="创建人"),
|
||||||
|
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2023-01-01 00:00:00"),
|
||||||
|
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2023-12-31 23:59:59"),
|
||||||
|
) -> None:
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# 模糊查询字段
|
||||||
|
self.name = ("like", name)
|
||||||
|
|
||||||
|
# 精确查询字段
|
||||||
|
self.creator_id = creator
|
||||||
|
self.status = status
|
||||||
|
|
||||||
|
# 时间范围查询
|
||||||
|
if start_time and end_time:
|
||||||
|
start_datetime = datetime.strptime(str(start_time), '%Y-%m-%d %H:%M:%S')
|
||||||
|
end_datetime = datetime.strptime(str(end_time), '%Y-%m-%d %H:%M:%S')
|
||||||
|
self.created_at = ("between", (start_datetime, end_datetime))
|
||||||
|
|
||||||
page_num: int = Field(default=1, description='当前页码')
|
|
||||||
page_size: int = Field(default=10, description='每页记录数')
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|||||||
from pydantic.alias_generators import to_camel
|
from pydantic.alias_generators import to_camel
|
||||||
from pydantic_validation_decorator import NotBlank
|
from pydantic_validation_decorator import NotBlank
|
||||||
|
|
||||||
from module_admin.annotation.pydantic_annotation import as_query
|
|
||||||
from utils.string_util import StringUtil
|
from utils.string_util import StringUtil
|
||||||
from app.common.constant import GenConstant
|
from app.common.constant import GenConstant
|
||||||
|
|
||||||
@@ -89,9 +88,9 @@ class GenTableSchema(GenTableBaseSchema):
|
|||||||
代码生成业务表模型
|
代码生成业务表模型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pk_column: Optional['GenTableColumnModel'] = Field(default=None, description='主键信息')
|
pk_column: Optional['GenTableColumnSchema'] = Field(default=None, description='主键信息')
|
||||||
sub_table: Optional['GenTableModel'] = Field(default=None, description='子表信息')
|
sub_table: Optional['GenTableSchema'] = Field(default=None, description='子表信息')
|
||||||
columns: Optional[List['GenTableColumnModel']] = Field(default=None, description='表列信息')
|
columns: Optional[List['GenTableColumnSchema']] = Field(default=None, description='表列信息')
|
||||||
tree_code: Optional[str] = Field(default=None, description='树编码字段')
|
tree_code: Optional[str] = Field(default=None, description='树编码字段')
|
||||||
tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段')
|
tree_parent_code: Optional[str] = Field(default=None, description='树父编码字段')
|
||||||
tree_name: Optional[str] = Field(default=None, description='树名称字段')
|
tree_name: Optional[str] = Field(default=None, description='树名称字段')
|
||||||
@@ -102,7 +101,7 @@ class GenTableSchema(GenTableBaseSchema):
|
|||||||
crud: Optional[bool] = Field(default=None, description='是否为单表')
|
crud: Optional[bool] = Field(default=None, description='是否为单表')
|
||||||
|
|
||||||
@model_validator(mode='after')
|
@model_validator(mode='after')
|
||||||
def check_some_is(self) -> 'GenTableModel':
|
def check_some_is(self) -> 'GenTableSchema':
|
||||||
self.sub = True if self.tpl_category and self.tpl_category == GenConstant.TPL_SUB else False
|
self.sub = True if self.tpl_category and self.tpl_category == GenConstant.TPL_SUB else False
|
||||||
self.tree = True if self.tpl_category and self.tpl_category == GenConstant.TPL_TREE else False
|
self.tree = True if self.tpl_category and self.tpl_category == GenConstant.TPL_TREE else False
|
||||||
self.crud = True if self.tpl_category and self.tpl_category == GenConstant.TPL_CRUD else False
|
self.crud = True if self.tpl_category and self.tpl_category == GenConstant.TPL_CRUD else False
|
||||||
@@ -114,7 +113,7 @@ class EditGenTableSchema(GenTableSchema):
|
|||||||
修改代码生成业务表模型
|
修改代码生成业务表模型
|
||||||
"""
|
"""
|
||||||
|
|
||||||
params: Optional['GenTableParamsModel'] = Field(default=None, description='业务表参数')
|
params: Optional['GenTableParamsSchema'] = Field(default=None, description='业务表参数')
|
||||||
|
|
||||||
|
|
||||||
class GenTableParamsSchema(BaseModel):
|
class GenTableParamsSchema(BaseModel):
|
||||||
@@ -130,15 +129,6 @@ class GenTableParamsSchema(BaseModel):
|
|||||||
parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段')
|
parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段')
|
||||||
|
|
||||||
|
|
||||||
class GenTableQuerySchema(GenTableBaseSchema):
|
|
||||||
"""
|
|
||||||
代码生成业务表不分页查询模型
|
|
||||||
"""
|
|
||||||
|
|
||||||
begin_time: Optional[str] = Field(default=None, description='开始时间')
|
|
||||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteGenTableSchema(BaseModel):
|
class DeleteGenTableSchema(BaseModel):
|
||||||
"""
|
"""
|
||||||
删除代码生成业务表模型
|
删除代码生成业务表模型
|
||||||
@@ -206,7 +196,7 @@ class GenTableColumnSchema(GenTableColumnBaseSchema):
|
|||||||
usable_column: Optional[bool] = Field(default=None, description='是否为基类字段白名单')
|
usable_column: Optional[bool] = Field(default=None, description='是否为基类字段白名单')
|
||||||
|
|
||||||
@model_validator(mode='after')
|
@model_validator(mode='after')
|
||||||
def check_some_is(self) -> 'GenTableModel':
|
def check_some_is(self) -> 'GenTableSchema':
|
||||||
self.cap_python_field = self.python_field[0].upper() + self.python_field[1:] if self.python_field else None
|
self.cap_python_field = self.python_field[0].upper() + self.python_field[1:] if self.python_field else None
|
||||||
self.pk = True if self.is_pk and self.is_pk == '1' else False
|
self.pk = True if self.is_pk and self.is_pk == '1' else False
|
||||||
self.increment = True if self.is_increment and self.is_increment == '1' else False
|
self.increment = True if self.is_increment and self.is_increment == '1' else False
|
||||||
@@ -227,15 +217,6 @@ class GenTableColumnSchema(GenTableColumnBaseSchema):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
class GenTableColumnQuerySchema(GenTableColumnBaseSchema):
|
|
||||||
"""
|
|
||||||
代码生成业务表字段不分页查询模型
|
|
||||||
"""
|
|
||||||
|
|
||||||
begin_time: Optional[str] = Field(default=None, description='开始时间')
|
|
||||||
end_time: Optional[str] = Field(default=None, description='结束时间')
|
|
||||||
|
|
||||||
|
|
||||||
class DeleteGenTableColumnSchema(BaseModel):
|
class DeleteGenTableColumnSchema(BaseModel):
|
||||||
"""
|
"""
|
||||||
删除代码生成业务表字段模型
|
删除代码生成业务表字段模型
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ async def get_obj_list_controller(
|
|||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:query"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:query"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await JobService.get_job_list_service(auth=auth, search=search, order_by=page.order_by)
|
result_dict_list = await JobService.get_job_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
||||||
logger.info(f"查询定时任务列表成功")
|
logger.info(f"查询定时任务列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询定时任务列表成功")
|
return SuccessResponse(data=result_dict, msg="查询定时任务列表成功")
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ async def get_job_log_list_controller(
|
|||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:query"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["monitor:job:query"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await JobLogService.get_job_log_list_service(auth=auth, search=search, order_by=page.order_by)
|
result_dict_list = await JobLogService.get_job_log_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
||||||
logger.info(f"查询定时任务日志列表成功")
|
logger.info(f"查询定时任务日志列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询定时任务日志列表成功")
|
return SuccessResponse(data=result_dict, msg="查询定时任务日志列表成功")
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ async def get_online_list_controller(
|
|||||||
)->JSONResponse:
|
)->JSONResponse:
|
||||||
# 获取全量数据
|
# 获取全量数据
|
||||||
result_dict_list = await OnlineService.get_online_list_service(redis=redis, search=search)
|
result_dict_list = await OnlineService.get_online_list_service(redis=redis, search=search)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= paging_query.page_no, page_size = paging_query.page_size)
|
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= paging_query.page_no, page_size = paging_query.page_size)
|
||||||
logger.info('获取成功')
|
logger.info('获取成功')
|
||||||
|
|
||||||
return SuccessResponse(data=result_dict,msg='获取成功')
|
return SuccessResponse(data=result_dict,msg='获取成功')
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ async def get_type_list_controller(
|
|||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:dict_type:query"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["system:dict_type:query"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await DictTypeService.get_obj_list_service(auth=auth, search=search, order_by=page.order_by)
|
result_dict_list = await DictTypeService.get_obj_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
||||||
logger.info(f"查询字典类型列表成功")
|
logger.info(f"查询字典类型列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询字典类型列表成功")
|
return SuccessResponse(data=result_dict, msg="查询字典类型列表成功")
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ async def get_data_list_controller(
|
|||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:dict_data:query"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["system:dict_data:query"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await DictDataService.get_obj_list_service(auth=auth, search=search, order_by=page.order_by)
|
result_dict_list = await DictDataService.get_obj_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
||||||
logger.info(f"查询字典数据列表成功")
|
logger.info(f"查询字典数据列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询字典数据列表成功")
|
return SuccessResponse(data=result_dict, msg="查询字典数据列表成功")
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ async def get_obj_list_controller(
|
|||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
""" 查询日志 """
|
""" 查询日志 """
|
||||||
result_dict_list = await OperationLogService.get_log_list_service(search=search, auth=auth, order_by=page.order_by)
|
result_dict_list = await OperationLogService.get_log_list_service(search=search, auth=auth, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
||||||
logger.info(f"查询日志成功")
|
logger.info(f"查询日志成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询日志成功")
|
return SuccessResponse(data=result_dict, msg="查询日志成功")
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ async def get_obj_list_controller(
|
|||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:notice:query"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["system:notice:query"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await NoticeService.get_notice_list_service(auth=auth, search=search, order_by=page.order_by)
|
result_dict_list = await NoticeService.get_notice_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
||||||
logger.info(f"查询公告列表成功")
|
logger.info(f"查询公告列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询公告列表成功")
|
return SuccessResponse(data=result_dict, msg="查询公告列表成功")
|
||||||
|
|
||||||
@@ -103,6 +103,6 @@ async def get_obj_list_available_controller(
|
|||||||
auth: AuthSchema = Depends(get_current_user)
|
auth: AuthSchema = Depends(get_current_user)
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await NoticeService.get_notice_list_available_service(auth=auth)
|
result_dict_list = await NoticeService.get_notice_list_available_service(auth=auth)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list)
|
result_dict = await PaginationService.paginate(data_list= result_dict_list)
|
||||||
logger.info(f"查询已启用公告列表成功")
|
logger.info(f"查询已启用公告列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询已启用公告列表成功")
|
return SuccessResponse(data=result_dict, msg="查询已启用公告列表成功")
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ async def get_obj_list_controller(
|
|||||||
search: ParamsQueryParam = Depends(),
|
search: ParamsQueryParam = Depends(),
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await ParamsService.get_obj_list_service(auth=auth, search=search, order_by=page.order_by)
|
result_dict_list = await ParamsService.get_obj_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
||||||
logger.info(f"获取参数列表成功")
|
logger.info(f"获取参数列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询参数列表成功")
|
return SuccessResponse(data=result_dict, msg="查询参数列表成功")
|
||||||
|
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ async def get_obj_list_controller(
|
|||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:position:query"])),
|
auth: AuthSchema = Depends(AuthPermission(permissions=["system:position:query"])),
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await PositionService.get_position_list_service(search=search, auth=auth, order_by=page.order_by)
|
result_dict_list = await PositionService.get_position_list_service(search=search, auth=auth, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
||||||
logger.info(f"查询岗位列表成功")
|
logger.info(f"查询岗位列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询岗位列表成功")
|
return SuccessResponse(data=result_dict, msg="查询岗位列表成功")
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ async def get_obj_list_controller(
|
|||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:role:query"])),
|
auth: AuthSchema = Depends(AuthPermission(permissions=["system:role:query"])),
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await RoleService.get_role_list_service(search=search, auth=auth, order_by=page.order_by)
|
result_dict_list = await RoleService.get_role_list_service(search=search, auth=auth, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
||||||
logger.info(f"查询角色成功")
|
logger.info(f"查询角色成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询角色成功")
|
return SuccessResponse(data=result_dict, msg="查询角色成功")
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ async def get_ticket_list_controller(
|
|||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:query"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["system:ticket:query"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await TicketService.get_ticket_list_service(auth=auth, search=search, order_by=page.order_by)
|
result_dict_list = await TicketService.get_ticket_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
||||||
logger.info("查询工单列表成功")
|
logger.info("查询工单列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询工单列表成功")
|
return SuccessResponse(data=result_dict, msg="查询工单列表成功")
|
||||||
|
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ async def get_obj_list_controller(
|
|||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:user:query"])),
|
auth: AuthSchema = Depends(AuthPermission(permissions=["system:user:query"])),
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await UserService.get_user_list_service(search=search, auth=auth, order_by=page.order_by)
|
result_dict_list = await UserService.get_user_list_service(search=search, auth=auth, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
result_dict = await PaginationService.paginate(data_list= result_dict_list, page_no= page.page_no, page_size = page.page_size)
|
||||||
logger.info(f"查询用户成功")
|
logger.info(f"查询用户成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询用户成功")
|
return SuccessResponse(data=result_dict, msg="查询用户成功")
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ from .schema import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class UserService:
|
class UserService:
|
||||||
"""用户模块服务层"""
|
"""用户模块服务层"""
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ async def get_version_list_controller(
|
|||||||
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:query"]))
|
auth: AuthSchema = Depends(AuthPermission(permissions=["system:version:query"]))
|
||||||
) -> JSONResponse:
|
) -> JSONResponse:
|
||||||
result_dict_list = await VersionService.get_version_list_service(auth=auth, search=search, order_by=page.order_by)
|
result_dict_list = await VersionService.get_version_list_service(auth=auth, search=search, order_by=page.order_by)
|
||||||
result_dict = await PaginationService.get_page_obj(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
result_dict = await PaginationService.paginate(data_list=result_dict_list, page_no=page.page_no, page_size=page.page_size)
|
||||||
logger.info("查询版本列表成功")
|
logger.info("查询版本列表成功")
|
||||||
return SuccessResponse(data=result_dict, msg="查询版本列表成功")
|
return SuccessResponse(data=result_dict, msg="查询版本列表成功")
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class PaginationService:
|
|||||||
"""分页服务类"""
|
"""分页服务类"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def get_page_obj(data_list: List[Any], page_no: Optional[int] = None, page_size: Optional[int] = None) -> Dict[str, Any]:
|
async def paginate(data_list: List[Any], page_no: Optional[int] = None, page_size: Optional[int] = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
输入数据列表data_list和分页信息,返回分页或非分页数据列表结果。
|
输入数据列表data_list和分页信息,返回分页或非分页数据列表结果。
|
||||||
如果未传入page_no和page_size,则返回全部数据。
|
如果未传入page_no和page_size,则返回全部数据。
|
||||||
|
|||||||
@@ -275,7 +275,6 @@ class Settings(BaseSettings):
|
|||||||
"app.core.middlewares.CustomCORSMiddleware" if self.CORS_ORIGIN_ENABLE else None,
|
"app.core.middlewares.CustomCORSMiddleware" if self.CORS_ORIGIN_ENABLE else None,
|
||||||
"app.core.middlewares.RequestLogMiddleware" if self.OPERATION_LOG_RECORD else None,
|
"app.core.middlewares.RequestLogMiddleware" if self.OPERATION_LOG_RECORD else None,
|
||||||
"app.core.middlewares.CustomGZipMiddleware" if self.GZIP_ENABLE else None,
|
"app.core.middlewares.CustomGZipMiddleware" if self.GZIP_ENABLE else None,
|
||||||
"app.core.middlewares.DemoEnvMiddleware" if self.DEMO_ENABLE else None,
|
|
||||||
]
|
]
|
||||||
return MIDDLEWARES
|
return MIDDLEWARES
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from typing import TypeVar, Sequence, Generic, Dict, Any, List, Union, Optional
|
from typing import TypeVar, Sequence, Generic, Dict, Any, List, Union, Optional
|
||||||
from sqlalchemy.sql.elements import ColumnElement
|
from sqlalchemy.sql.elements import ColumnElement
|
||||||
from sqlalchemy.orm import selectinload, DeclarativeBase
|
from sqlalchemy.orm import Session, selectinload, DeclarativeBase
|
||||||
from sqlalchemy.engine import Result
|
from sqlalchemy.engine import Result
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import asc, func, select, delete, Select, desc, update, or_, and_
|
from sqlalchemy import asc, func, select, delete, Select, desc, update, or_, and_
|
||||||
@@ -32,7 +32,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
|||||||
"""
|
"""
|
||||||
self.model = model
|
self.model = model
|
||||||
self.auth = auth
|
self.auth = auth
|
||||||
self.db: AsyncSession = auth.db
|
self.db: AsyncSession | Session | None = auth.db
|
||||||
self.current_user = auth.user
|
self.current_user = auth.user
|
||||||
|
|
||||||
async def get(self, **kwargs) -> Optional[ModelType]:
|
async def get(self, **kwargs) -> Optional[ModelType]:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from fastapi import Depends, Request
|
|||||||
from motor.motor_asyncio import AsyncIOMotorDatabase
|
from motor.motor_asyncio import AsyncIOMotorDatabase
|
||||||
from fastapi import Depends
|
from fastapi import Depends
|
||||||
|
|
||||||
|
from app.api.v1.module_system.user.schema import UserOutSchema
|
||||||
from app.common.enums import RedisInitKeyConfig
|
from app.common.enums import RedisInitKeyConfig
|
||||||
from app.core.exceptions import CustomException
|
from app.core.exceptions import CustomException
|
||||||
from app.core.database import session_connect
|
from app.core.database import session_connect
|
||||||
@@ -95,7 +96,7 @@ async def get_current_user(
|
|||||||
if hasattr(user, 'positions'):
|
if hasattr(user, 'positions'):
|
||||||
user.positions = [pos for pos in user.positions if pos.status]
|
user.positions = [pos for pos in user.positions if pos.status]
|
||||||
|
|
||||||
auth.user = user
|
auth.user = UserOutSchema.model_validate(user)
|
||||||
return auth
|
return auth
|
||||||
|
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ class AuthPermission:
|
|||||||
auth.check_data_scope = self.check_data_scope
|
auth.check_data_scope = self.check_data_scope
|
||||||
|
|
||||||
# 超级管理员直接通过
|
# 超级管理员直接通过
|
||||||
if auth.user.is_superuser:
|
if auth.user and auth.user.is_superuser:
|
||||||
return auth
|
return auth
|
||||||
|
|
||||||
# 无需验证权限
|
# 无需验证权限
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from typing import Any, Optional, List, Tuple, Union
|
from typing import Any, Optional
|
||||||
from fastapi import Request, status
|
from fastapi import Request, status
|
||||||
from fastapi.exceptions import RequestValidationError, ResponseValidationError
|
from fastapi.exceptions import RequestValidationError, ResponseValidationError
|
||||||
from pydantic_validation_decorator import FieldValidationError
|
from pydantic_validation_decorator import FieldValidationError
|
||||||
from starlette.responses import JSONResponse
|
from starlette.responses import JSONResponse
|
||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from pydantic_core import ErrorDetails
|
|
||||||
from pydantic import ValidationError
|
|
||||||
|
|
||||||
from app.common.constant import RET
|
from app.common.constant import RET
|
||||||
from app.common.response import ErrorResponse
|
from app.common.response import ErrorResponse
|
||||||
@@ -20,7 +18,7 @@ class CustomException(Exception):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
msg: Optional[str] = RET.EXCEPTION.msg,
|
msg: str = RET.EXCEPTION.msg,
|
||||||
code: int = RET.EXCEPTION.code,
|
code: int = RET.EXCEPTION.code,
|
||||||
status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR,
|
status_code: int = status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
data: Optional[Any] = None,
|
data: Optional[Any] = None,
|
||||||
@@ -59,15 +57,23 @@ async def HttpExceptionHandler(request: Request, exc: HTTPException) -> JSONResp
|
|||||||
|
|
||||||
async def ValidationExceptionHandler(request: Request, exc: RequestValidationError) -> JSONResponse:
|
async def ValidationExceptionHandler(request: Request, exc: RequestValidationError) -> JSONResponse:
|
||||||
"""请求参数验证异常处理器"""
|
"""请求参数验证异常处理器"""
|
||||||
msg:List[ErrorDetails] = custom_convert_errors(exc)
|
error_mapping = {
|
||||||
|
"Field required": "请求失败,缺少必填项!",
|
||||||
|
"value is not a valid list": "类型错误,提交参数应该为列表!",
|
||||||
|
"value is not a valid int": "类型错误,提交参数应该为整数!",
|
||||||
|
"value could not be parsed to a boolean": "类型错误,提交参数应该为布尔值!",
|
||||||
|
"Input should be a valid list": "类型错误,输入应该是一个有效的列表!"
|
||||||
|
}
|
||||||
|
msg = error_mapping.get(exc.errors()[0].get('msg'), exc.errors()[0].get('msg'))
|
||||||
logger.error(f"请求地址: {request.url}, 错误信息: {msg}, 错误详情: {exc}")
|
logger.error(f"请求地址: {request.url}, 错误信息: {msg}, 错误详情: {exc}")
|
||||||
return ErrorResponse(msg=str(msg), status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, data=exc.body)
|
return ErrorResponse(msg=str(msg), status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, data=exc.body)
|
||||||
|
|
||||||
|
|
||||||
async def ResponseValidationHandle(request: Request, exc: ResponseValidationError) -> JSONResponse:
|
async def ResponseValidationHandle(request: Request, exc: ResponseValidationError) -> JSONResponse:
|
||||||
logger.error(f"请求地址: {request.url}, 错误详情: {exc}")
|
logger.error(f"请求地址: {request.url}, 错误详情: {exc}")
|
||||||
return ErrorResponse(msg=str(exc), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, data=exc.body)
|
return ErrorResponse(msg=str(exc), status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, data=exc.body)
|
||||||
|
|
||||||
|
|
||||||
async def SQLAlchemyExceptionHandler(request: Request, exc: SQLAlchemyError) -> JSONResponse:
|
async def SQLAlchemyExceptionHandler(request: Request, exc: SQLAlchemyError) -> JSONResponse:
|
||||||
"""数据库异常处理器"""
|
"""数据库异常处理器"""
|
||||||
error_msg = f'数据库操作失败: {exc}'
|
error_msg = f'数据库操作失败: {exc}'
|
||||||
@@ -91,75 +97,3 @@ async def AllExceptionHandler(request: Request, exc: Exception) -> JSONResponse:
|
|||||||
"""全局异常处理器"""
|
"""全局异常处理器"""
|
||||||
logger.error(f"请求地址: {request.url}, 错误详情: {exc}")
|
logger.error(f"请求地址: {request.url}, 错误详情: {exc}")
|
||||||
return ErrorResponse(msg='服务器内部错误', status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, data=str(exc))
|
return ErrorResponse(msg='服务器内部错误', status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, data=str(exc))
|
||||||
|
|
||||||
ERROR_MAPPING = {
|
|
||||||
"missing": "请求失败,缺少必填项!",
|
|
||||||
# 字符串
|
|
||||||
"string_pattern_mismatch": "值错误,提交参数不满足正则表达式{pattern}!",
|
|
||||||
"string_too_long": "值错误,提交参数长度必须小于等于{max_length}!",
|
|
||||||
"string_too_short": "值错误,提交参数长度必须大于等于{min_length}!",
|
|
||||||
"string_type": "类型错误,提交参数应该为字符串!",
|
|
||||||
# 列表
|
|
||||||
"list_type": "类型错误,提交参数应该为列表!",
|
|
||||||
# 字典
|
|
||||||
"dict_type": "类型错误,提交参数应该为字典!",
|
|
||||||
# 集合
|
|
||||||
"set_type": "类型错误,提交参数应该为集合!",
|
|
||||||
# 元组
|
|
||||||
"tuple_type": "类型错误,提交参数应该为元组!",
|
|
||||||
# 元素数量
|
|
||||||
"too_long": "数量错误,提交参数的元素数量必须小于等于{max_length}!",
|
|
||||||
"too_short": "数量错误,提交参数的元素数量必须大于等于{min_length}!",
|
|
||||||
# 大小比值
|
|
||||||
"less_than_equal": "值错误,提交参数必须小于等于{le}!",
|
|
||||||
"greater_than_equal": "值错误,提交参数必须大于等于{ge}!",
|
|
||||||
"less_than": "值错误,提交参数必须小于{lt}!",
|
|
||||||
"greater_than": "值错误,提交参数必须大于{gt}!",
|
|
||||||
# 布尔值
|
|
||||||
"bool_type": "类型错误,提交参数应该为布尔值!",
|
|
||||||
"bool_parsing": "类型错误,提交参数应该为布尔值!",
|
|
||||||
# 字节
|
|
||||||
"bytes_type": "类型错误,提交参数应该为字节!",
|
|
||||||
"bytes_too_long": "值错误,提交参数长度必须小于等于{max_length}!",
|
|
||||||
"bytes_too_short": "值错误,提交参数长度必须大于等于{min_length}!",
|
|
||||||
# 整数
|
|
||||||
"int_parsing": "类型错误,提交参数应该为整数!",
|
|
||||||
"int_type": "类型错误,提交参数应该为整数!",
|
|
||||||
# 浮点数
|
|
||||||
"float_parsing": "类型错误,提交参数应该为浮点数!",
|
|
||||||
"float_type": "类型错误,提交参数应该为浮点数!",
|
|
||||||
# 日期时间
|
|
||||||
"date_parsing": "类型错误,提交参数应该为日期!",
|
|
||||||
"date_type": "类型错误,提交参数应该为日期!",
|
|
||||||
"time_parsing": "类型错误,提交参数应该为时间!",
|
|
||||||
"time_type": "类型错误,提交参数应该为时间!",
|
|
||||||
# 其他
|
|
||||||
"literal_error": "值错误,提交参数值在为{expected}中一个!",
|
|
||||||
"extra_forbidden": "值错误,提交参数值不在允许范围内!",
|
|
||||||
}
|
|
||||||
|
|
||||||
def custom_convert_errors(e: ValidationError | RequestValidationError) -> List[ErrorDetails]:
|
|
||||||
new_errors: List[ErrorDetails] = []
|
|
||||||
for error in e.errors():
|
|
||||||
error['loc'] = loc_to_dot_sep(error['loc'])
|
|
||||||
custom_message = ERROR_MAPPING.get(error['type'])
|
|
||||||
if custom_message:
|
|
||||||
ctx = error.get('ctx')
|
|
||||||
error['msg'] = (
|
|
||||||
custom_message.format(**ctx) if ctx else custom_message
|
|
||||||
)
|
|
||||||
new_errors.append(error)
|
|
||||||
return new_errors
|
|
||||||
|
|
||||||
def loc_to_dot_sep(loc: Tuple[Union[str, int], ...]) -> str:
|
|
||||||
path = ''
|
|
||||||
for i, x in enumerate(loc):
|
|
||||||
if isinstance(x, str):
|
|
||||||
if i > 0:
|
|
||||||
path += '.'
|
|
||||||
path += x
|
|
||||||
elif isinstance(x, int):
|
|
||||||
path += f'[{x}]'
|
|
||||||
else:
|
|
||||||
raise TypeError('Unexpected type')
|
|
||||||
return path
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import logging
|
|||||||
from logging.handlers import TimedRotatingFileHandler
|
from logging.handlers import TimedRotatingFileHandler
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import typing
|
||||||
|
|
||||||
from app.config.setting import settings
|
from app.config.setting import settings
|
||||||
|
|
||||||
@@ -29,7 +30,7 @@ class CustomTimedRotatingFileHandler(TimedRotatingFileHandler):
|
|||||||
# 使用流上下文管理确保资源正确释放
|
# 使用流上下文管理确保资源正确释放
|
||||||
if self.stream:
|
if self.stream:
|
||||||
self.stream.close()
|
self.stream.close()
|
||||||
self.stream = None
|
self.stream = None # type: ignore
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 计算轮换时间(使用缓存避免重复计算)
|
# 计算轮换时间(使用缓存避免重复计算)
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from typing import Dict, List, Union
|
from typing import Any
|
||||||
from starlette.middleware.cors import CORSMiddleware
|
from starlette.middleware.cors import CORSMiddleware
|
||||||
from starlette.types import ASGIApp
|
from starlette.types import ASGIApp
|
||||||
from starlette.requests import Request
|
from starlette.requests import Request
|
||||||
from starlette.middleware.gzip import GZipMiddleware
|
from starlette.middleware.gzip import GZipMiddleware
|
||||||
from starlette.middleware.base import Response, BaseHTTPMiddleware, RequestResponseEndpoint
|
from starlette.responses import Response
|
||||||
|
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
|
||||||
|
|
||||||
from app.common.response import ErrorResponse
|
from app.common.response import ErrorResponse
|
||||||
from app.config.setting import settings
|
from app.config.setting import settings
|
||||||
@@ -17,7 +18,7 @@ from app.core.exceptions import CustomException
|
|||||||
class CustomCORSMiddleware(CORSMiddleware):
|
class CustomCORSMiddleware(CORSMiddleware):
|
||||||
"""CORS跨域中间件"""
|
"""CORS跨域中间件"""
|
||||||
def __init__(self, app: ASGIApp) -> None:
|
def __init__(self, app: ASGIApp) -> None:
|
||||||
CORSMiddlewareConfig: Dict[str, Union[List[str], bool]] = {
|
CORSMiddlewareConfig: dict[str, Any] = {
|
||||||
"allow_origins": settings.ALLOW_ORIGINS,
|
"allow_origins": settings.ALLOW_ORIGINS,
|
||||||
"allow_methods": settings.ALLOW_METHODS,
|
"allow_methods": settings.ALLOW_METHODS,
|
||||||
"allow_headers": settings.ALLOW_HEADERS,
|
"allow_headers": settings.ALLOW_HEADERS,
|
||||||
@@ -38,65 +39,59 @@ class RequestLogMiddleware(BaseHTTPMiddleware):
|
|||||||
) -> Response:
|
) -> Response:
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
logger.info(
|
# 构建请求日志信息
|
||||||
f"请求来源: {request.client.host}, "
|
request_info = f"请求方法: {request.method}, 请求路径: {request.url.path}"
|
||||||
f"请求方法: {request.method}, "
|
if request.client:
|
||||||
f"请求路径: {request.url.path}, "
|
request_info = f"请求来源: {request.client.host}, {request_info}"
|
||||||
f"客户端IP: {request.client.host}"
|
logger.info(request_info)
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = await call_next(request)
|
|
||||||
|
if settings.DEMO_ENABLE:
|
||||||
|
# 在演示环境中,只有白名单内的IP或路径才能执行非GET请求
|
||||||
|
if request.method != "GET":
|
||||||
|
path = request.scope.get("path")
|
||||||
|
|
||||||
|
request_ip = None
|
||||||
|
x_forwarded_for = request.headers.get('X-Forwarded-For')
|
||||||
|
if x_forwarded_for:
|
||||||
|
# 取第一个 IP 地址,通常为客户端真实 IP
|
||||||
|
request_ip = x_forwarded_for.split(',')[0].strip()
|
||||||
|
else:
|
||||||
|
# 若没有 X-Forwarded-For 头,则使用 request.client.host
|
||||||
|
request_ip = request.client.host if request.client else None
|
||||||
|
|
||||||
|
# 检查IP是否在白名单,或路径是否在白名单,或用户是否在白名单
|
||||||
|
if (request_ip in settings.DEMO_IP_WHITE_LIST) or (path in settings.DEMO_WHITE_LIST_PATH):
|
||||||
|
response = await call_next(request)
|
||||||
|
else:
|
||||||
|
# 非白名单用户,禁止操作
|
||||||
|
return ErrorResponse(msg="演示环境,禁止操作")
|
||||||
|
else:
|
||||||
|
# GET请求在演示环境中总是允许的
|
||||||
|
response = await call_next(request)
|
||||||
|
else:
|
||||||
|
# 非演示环境,正常处理请求
|
||||||
|
response = await call_next(request)
|
||||||
process_time = round(time.time() - start_time, 5)
|
process_time = round(time.time() - start_time, 5)
|
||||||
response.headers["X-Process-Time"] = str(process_time)
|
response.headers["X-Process-Time"] = str(process_time)
|
||||||
|
|
||||||
logger.info(
|
# 构建响应日志信息
|
||||||
f"会话ID: {request.scope.get('session_id')}, "
|
session_id = request.scope.get('session_id')
|
||||||
|
content_length = response.headers.get('content-length', '0')
|
||||||
|
response_info = (
|
||||||
|
f"会话ID: {session_id}, "
|
||||||
f"响应状态: {response.status_code}, "
|
f"响应状态: {response.status_code}, "
|
||||||
f"响应内容长度: {response.headers.get('content-length', '0')}, "
|
f"响应内容长度: {content_length}, "
|
||||||
f"处理时间: {process_time}s"
|
f"处理时间: {process_time}s"
|
||||||
)
|
)
|
||||||
|
logger.info(response_info)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
except CustomException as e:
|
except CustomException as e:
|
||||||
logger.error(f"系统异常: {str(e)}")
|
logger.error(f"系统异常: {str(e)}")
|
||||||
return ErrorResponse(msg=f"系统异常,请联系管理员: {str(e)}")
|
return ErrorResponse(msg=f"系统异常,请联系管理员", data=str(e))
|
||||||
|
|
||||||
|
|
||||||
class DemoEnvMiddleware(BaseHTTPMiddleware):
|
|
||||||
"""演示环境中间件"""
|
|
||||||
def __init__(self, app: ASGIApp) -> None:
|
|
||||||
super().__init__(app)
|
|
||||||
|
|
||||||
async def dispatch(
|
|
||||||
self, request: Request, call_next: RequestResponseEndpoint
|
|
||||||
) -> Response:
|
|
||||||
|
|
||||||
if settings.DEMO_ENABLE and request.method != "GET":
|
|
||||||
path = request.scope.get("path")
|
|
||||||
|
|
||||||
request_ip = None
|
|
||||||
x_forwarded_for = request.headers.get('X-Forwarded-For')
|
|
||||||
if x_forwarded_for:
|
|
||||||
# 取第一个 IP 地址,通常为客户端真实 IP
|
|
||||||
request_ip = x_forwarded_for.split(',')[0].strip()
|
|
||||||
else:
|
|
||||||
# 若没有 X-Forwarded-For 头,则使用 request.client.host
|
|
||||||
request_ip = request.client.host
|
|
||||||
|
|
||||||
user_username = request.scope.get("user_username")
|
|
||||||
logger.error(f"用户名称: {user_username}")
|
|
||||||
|
|
||||||
# 检查IP是否在白名单,或路径是否在白名单,或用户是否在白名单
|
|
||||||
if (request_ip in settings.DEMO_IP_WHITE_LIST) or (path in settings.DEMO_WHITE_LIST_PATH):
|
|
||||||
return await call_next(request)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# 非白名单用户,禁止操作
|
|
||||||
return ErrorResponse(msg="演示环境,禁止操作")
|
|
||||||
|
|
||||||
return await call_next(request)
|
|
||||||
|
|
||||||
|
|
||||||
class CustomGZipMiddleware(GZipMiddleware):
|
class CustomGZipMiddleware(GZipMiddleware):
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import inspect
|
||||||
|
from fastapi import Form, Query
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from pydantic.fields import FieldInfo
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
|
||||||
|
def as_query(cls: Type[BaseModel]):
|
||||||
|
"""
|
||||||
|
pydantic模型查询参数装饰器,将pydantic模型用于接收查询参数
|
||||||
|
"""
|
||||||
|
new_parameters = []
|
||||||
|
|
||||||
|
for field_name, model_field in cls.model_fields.items():
|
||||||
|
model_field: FieldInfo # type: ignore
|
||||||
|
|
||||||
|
if not model_field.is_required():
|
||||||
|
new_parameters.append(
|
||||||
|
inspect.Parameter(
|
||||||
|
model_field.alias,
|
||||||
|
inspect.Parameter.POSITIONAL_ONLY,
|
||||||
|
default=Query(default=model_field.default, description=model_field.description),
|
||||||
|
annotation=model_field.annotation,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
new_parameters.append(
|
||||||
|
inspect.Parameter(
|
||||||
|
model_field.alias,
|
||||||
|
inspect.Parameter.POSITIONAL_ONLY,
|
||||||
|
default=Query(..., description=model_field.description),
|
||||||
|
annotation=model_field.annotation,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def as_query_func(**data):
|
||||||
|
return cls(**data)
|
||||||
|
|
||||||
|
sig = inspect.signature(as_query_func)
|
||||||
|
sig = sig.replace(parameters=new_parameters)
|
||||||
|
as_query_func.__signature__ = sig # type: ignore
|
||||||
|
setattr(cls, 'as_query', as_query_func)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
|
def as_form(cls: Type[BaseModel]):
|
||||||
|
"""
|
||||||
|
pydantic模型表单参数装饰器,将pydantic模型用于接收表单参数
|
||||||
|
"""
|
||||||
|
new_parameters = []
|
||||||
|
|
||||||
|
for field_name, model_field in cls.model_fields.items():
|
||||||
|
model_field: FieldInfo # type: ignore
|
||||||
|
|
||||||
|
if not model_field.is_required():
|
||||||
|
new_parameters.append(
|
||||||
|
inspect.Parameter(
|
||||||
|
model_field.alias,
|
||||||
|
inspect.Parameter.POSITIONAL_ONLY,
|
||||||
|
default=Form(default=model_field.default, description=model_field.description),
|
||||||
|
annotation=model_field.annotation,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
new_parameters.append(
|
||||||
|
inspect.Parameter(
|
||||||
|
model_field.alias,
|
||||||
|
inspect.Parameter.POSITIONAL_ONLY,
|
||||||
|
default=Form(..., description=model_field.description),
|
||||||
|
annotation=model_field.annotation,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
async def as_form_func(**data):
|
||||||
|
return cls(**data)
|
||||||
|
|
||||||
|
sig = inspect.signature(as_form_func)
|
||||||
|
sig = sig.replace(parameters=new_parameters)
|
||||||
|
as_form_func.__signature__ = sig # type: ignore
|
||||||
|
setattr(cls, 'as_form', as_form_func)
|
||||||
|
return cls
|
||||||
@@ -148,7 +148,7 @@ class RedisCURD:
|
|||||||
async def hash_set(self, name: str, key: str, value: Any) -> bool:
|
async def hash_set(self, name: str, key: str, value: Any) -> bool:
|
||||||
"""设置哈希缓存"""
|
"""设置哈希缓存"""
|
||||||
try:
|
try:
|
||||||
await self.redis.hset(name=name, key=key, value=value)
|
self.redis.hset(name=name, key=key, value=value)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"设置哈希缓存失败: {str(e)}")
|
logger.error(f"设置哈希缓存失败: {str(e)}")
|
||||||
@@ -157,7 +157,7 @@ class RedisCURD:
|
|||||||
async def hash_get(self, name: str, keys: list[str]) -> Optional[list[Any]]:
|
async def hash_get(self, name: str, keys: list[str]) -> Optional[list[Any]]:
|
||||||
"""获取哈希缓存"""
|
"""获取哈希缓存"""
|
||||||
try:
|
try:
|
||||||
return await self.redis.hmget(name=name, keys=keys)
|
return self.redis.hmget(name=name, keys=keys)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"获取哈希缓存失败: {str(e)}")
|
logger.error(f"获取哈希缓存失败: {str(e)}")
|
||||||
return None
|
return None
|
||||||
@@ -35,7 +35,7 @@ class OperationLogRoute(APIRoute):
|
|||||||
return response
|
return response
|
||||||
if request.method not in settings.OPERATION_RECORD_METHOD:
|
if request.method not in settings.OPERATION_RECORD_METHOD:
|
||||||
return response
|
return response
|
||||||
route: APIRoute = request.scope.get("route")
|
route: APIRoute = request.scope.get("route", None)
|
||||||
if route.name in settings.IGNORE_OPERATION_FUNCTION:
|
if route.name in settings.IGNORE_OPERATION_FUNCTION:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@@ -66,7 +66,6 @@ class OperationLogRoute(APIRoute):
|
|||||||
oper_param['path_params'] = dict(path_params)
|
oper_param['path_params'] = dict(path_params)
|
||||||
|
|
||||||
payload = json.dumps(oper_param, ensure_ascii=False)
|
payload = json.dumps(oper_param, ensure_ascii=False)
|
||||||
# payload = str(oper_param)
|
|
||||||
|
|
||||||
# 日志表请求参数字段长度最大为2000,因此在此处判断长度
|
# 日志表请求参数字段长度最大为2000,因此在此处判断长度
|
||||||
if len(payload) > 2000:
|
if len(payload) > 2000:
|
||||||
@@ -89,19 +88,18 @@ class OperationLogRoute(APIRoute):
|
|||||||
request_ip = x_forwarded_for.split(',')[0].strip()
|
request_ip = x_forwarded_for.split(',')[0].strip()
|
||||||
else:
|
else:
|
||||||
# 若没有 X-Forwarded-For 头,则使用 request.client.host
|
# 若没有 X-Forwarded-For 头,则使用 request.client.host
|
||||||
request_ip = request.client.host
|
if request.client:
|
||||||
|
request_ip = request.client.host
|
||||||
|
|
||||||
login_location = await IpLocalUtil.get_ip_location(request_ip)
|
login_location = await IpLocalUtil.get_ip_location(request_ip) if request_ip else None
|
||||||
|
|
||||||
# 判断请求是否来自api文档
|
# 判断请求是否来自api文档
|
||||||
request_from_swagger = (
|
referer = request.headers.get('referer')
|
||||||
request.headers.get('referer').endswith('docs') if request.headers.get('referer') else False
|
request_from_swagger = referer and referer.endswith('docs')
|
||||||
)
|
request_from_redoc = referer and referer.endswith('redoc')
|
||||||
request_from_redoc = (
|
|
||||||
request.headers.get('referer').endswith('redoc') if request.headers.get('referer') else False
|
|
||||||
)
|
|
||||||
|
|
||||||
if request_from_swagger or request_from_redoc:
|
if request_from_swagger or request_from_redoc:
|
||||||
|
# 如果请求来自api文档,则不记录日志
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
async with session_connect() as session:
|
async with session_connect() as session:
|
||||||
@@ -117,7 +115,7 @@ class OperationLogRoute(APIRoute):
|
|||||||
request_os = user_agent.os.family,
|
request_os = user_agent.os.family,
|
||||||
request_browser = user_agent.browser.family,
|
request_browser = user_agent.browser.family,
|
||||||
response_code = response.status_code,
|
response_code = response.status_code,
|
||||||
response_json = response_data.decode(),
|
response_json = response_data.decode() if isinstance(response_data, (bytes, bytearray)) else str(response_data),
|
||||||
process_time = process_time,
|
process_time = process_time,
|
||||||
description = route.summary,
|
description = route.summary,
|
||||||
creator_id = current_user_id
|
creator_id = current_user_id
|
||||||
|
|||||||
@@ -2256,5 +2256,45 @@
|
|||||||
"description": "前端构建"
|
"description": "前端构建"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "流程管理",
|
||||||
|
"type": 1,
|
||||||
|
"icon": "el-icon-ShoppingBag",
|
||||||
|
"order": 10,
|
||||||
|
"permission": null,
|
||||||
|
"route_name": "Workflow",
|
||||||
|
"route_path": "/workflow",
|
||||||
|
"component_path": null,
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": false,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "流程管理",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": "/workflow/operator",
|
||||||
|
"description": "流程管理",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "我的流程",
|
||||||
|
"type": 2,
|
||||||
|
"icon": "el-icon-ShoppingBag",
|
||||||
|
"order": 1,
|
||||||
|
"permission": "workflow:operator:query",
|
||||||
|
"route_name": "Operator",
|
||||||
|
"route_path": "/workflow/operator",
|
||||||
|
"component_path": "workflow/operator/index",
|
||||||
|
"status": true,
|
||||||
|
"keep_alive": true,
|
||||||
|
"hidden": false,
|
||||||
|
"always_show": false,
|
||||||
|
"title": "我的流程",
|
||||||
|
"params": null,
|
||||||
|
"affix": false,
|
||||||
|
"redirect": null,
|
||||||
|
"description": "我的流程"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -430,5 +430,45 @@
|
|||||||
{
|
{
|
||||||
"role_id": 1,
|
"role_id": 1,
|
||||||
"menu_id": 108
|
"menu_id": 108
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role_id": 1,
|
||||||
|
"menu_id": 109
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role_id": 1,
|
||||||
|
"menu_id": 110
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role_id": 1,
|
||||||
|
"menu_id": 111
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role_id": 1,
|
||||||
|
"menu_id": 112
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role_id": 1,
|
||||||
|
"menu_id": 113
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role_id": 1,
|
||||||
|
"menu_id": 114
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role_id": 1,
|
||||||
|
"menu_id": 115
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role_id": 1,
|
||||||
|
"menu_id": 116
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role_id": 1,
|
||||||
|
"menu_id": 117
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"role_id": 1,
|
||||||
|
"menu_id": 118
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from typing import Any, Sequence
|
|
||||||
|
|
||||||
from backend.common.enums import BuildTreeType
|
|
||||||
from backend.utils.serializers import RowData, select_list_serialize
|
|
||||||
|
|
||||||
|
|
||||||
def get_tree_nodes(row: Sequence[RowData], is_sort: bool, sort_key: str) -> list[dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
获取所有树形结构节点
|
|
||||||
|
|
||||||
:param row: 原始数据行序列
|
|
||||||
:param is_sort: 是否启用结果排序
|
|
||||||
:param sort_key: 基于此键对结果进行进行排序
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
tree_nodes = select_list_serialize(row)
|
|
||||||
if is_sort:
|
|
||||||
tree_nodes.sort(key=lambda x: x[sort_key])
|
|
||||||
return tree_nodes
|
|
||||||
|
|
||||||
|
|
||||||
def traversal_to_tree(nodes: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
通过遍历算法构造树形结构
|
|
||||||
|
|
||||||
:param nodes: 树节点列表
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
tree: list[dict[str, Any]] = []
|
|
||||||
node_dict = {node['id']: node for node in nodes}
|
|
||||||
|
|
||||||
for node in nodes:
|
|
||||||
parent_id = node['parent_id']
|
|
||||||
if parent_id is None:
|
|
||||||
tree.append(node)
|
|
||||||
else:
|
|
||||||
parent_node = node_dict.get(parent_id)
|
|
||||||
if parent_node is not None:
|
|
||||||
if 'children' not in parent_node:
|
|
||||||
parent_node['children'] = []
|
|
||||||
if node not in parent_node['children']:
|
|
||||||
parent_node['children'].append(node)
|
|
||||||
else:
|
|
||||||
if node not in tree:
|
|
||||||
tree.append(node)
|
|
||||||
|
|
||||||
return tree
|
|
||||||
|
|
||||||
|
|
||||||
def recursive_to_tree(nodes: list[dict[str, Any]], *, parent_id: int | None = None) -> list[dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
通过递归算法构造树形结构(性能影响较大)
|
|
||||||
|
|
||||||
:param nodes: 树节点列表
|
|
||||||
:param parent_id: 父节点 ID,默认为 None 表示根节点
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
tree: list[dict[str, Any]] = []
|
|
||||||
for node in nodes:
|
|
||||||
if node['parent_id'] == parent_id:
|
|
||||||
child_nodes = recursive_to_tree(nodes, parent_id=node['id'])
|
|
||||||
if child_nodes:
|
|
||||||
node['children'] = child_nodes
|
|
||||||
tree.append(node)
|
|
||||||
return tree
|
|
||||||
|
|
||||||
|
|
||||||
def get_tree_data(
|
|
||||||
row: Sequence[RowData],
|
|
||||||
build_type: BuildTreeType = BuildTreeType.traversal,
|
|
||||||
*,
|
|
||||||
parent_id: int | None = None,
|
|
||||||
is_sort: bool = True,
|
|
||||||
sort_key: str = 'sort',
|
|
||||||
) -> list[dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
获取树形结构数据
|
|
||||||
|
|
||||||
:param row: 原始数据行序列
|
|
||||||
:param build_type: 构建树形结构的算法类型,默认为遍历算法
|
|
||||||
:param parent_id: 父节点 ID,仅在递归算法中使用
|
|
||||||
:param is_sort: 是否启用结果排序
|
|
||||||
:param sort_key: 基于此键对结果进行进行排序
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
nodes = get_tree_nodes(row, is_sort, sort_key)
|
|
||||||
match build_type:
|
|
||||||
case BuildTreeType.traversal:
|
|
||||||
tree = traversal_to_tree(nodes)
|
|
||||||
case BuildTreeType.recursive:
|
|
||||||
tree = recursive_to_tree(nodes, parent_id=parent_id)
|
|
||||||
case _:
|
|
||||||
raise ValueError(f'无效的算法类型:{build_type}')
|
|
||||||
return tree
|
|
||||||
|
|
||||||
|
|
||||||
def get_vben5_tree_data(row: Sequence[RowData], is_sort: bool = True, sort_key: str = 'sort') -> list[dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
获取 vben5 菜单树形结构数据
|
|
||||||
|
|
||||||
:param row: 原始数据行序列
|
|
||||||
:param is_sort: 是否启用结果排序
|
|
||||||
:param sort_key: 基于此键对结果进行进行排序
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
meta_keys = {'title', 'icon', 'link', 'cache', 'display', 'status'}
|
|
||||||
|
|
||||||
vben5_nodes = [
|
|
||||||
{
|
|
||||||
**{k: v for k, v in node.items() if k not in meta_keys},
|
|
||||||
'meta': {
|
|
||||||
'title': node['title'],
|
|
||||||
'icon': node['icon'],
|
|
||||||
'iframeSrc': node['link'] if node['type'] == 3 else '',
|
|
||||||
'link': node['link'] if node['type'] == 4 else '',
|
|
||||||
'keepAlive': node['cache'],
|
|
||||||
'hideInMenu': not bool(node['display']),
|
|
||||||
'menuVisibleWithForbidden': not bool(node['status']),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for node in get_tree_nodes(row, is_sort, sort_key)
|
|
||||||
]
|
|
||||||
|
|
||||||
return traversal_to_tree(vben5_nodes)
|
|
||||||
@@ -199,6 +199,7 @@ def bytes2human(n: int, format_str: str = '%(value).1f%(symbol)s') -> str:
|
|||||||
return format_str % locals()
|
return format_str % locals()
|
||||||
return format_str % dict(symbol=symbols[0], value=n)
|
return format_str % dict(symbol=symbols[0], value=n)
|
||||||
|
|
||||||
|
|
||||||
def bytes2file_response(bytes_info: bytes):
|
def bytes2file_response(bytes_info: bytes):
|
||||||
"""生成文件响应"""
|
"""生成文件响应"""
|
||||||
yield bytes_info
|
yield bytes_info
|
||||||
@@ -218,3 +219,198 @@ def get_filepath_from_url(url: str):
|
|||||||
filepath = setting.settings.STATIC_ROOT.joinpath(task_path, task_id, file_name)
|
filepath = setting.settings.STATIC_ROOT.joinpath(task_path, task_id, file_name)
|
||||||
|
|
||||||
return filepath
|
return filepath
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class SqlalchemyUtil:
|
||||||
|
"""
|
||||||
|
sqlalchemy工具类
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def base_to_dict(
|
||||||
|
cls, obj: Union[Base, Dict], transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case'
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
将sqlalchemy模型对象转换为字典
|
||||||
|
|
||||||
|
:param obj: sqlalchemy模型对象或普通字典
|
||||||
|
:param transform_case: 转换得到的结果形式,可选的有'no_case'(不转换)、'snake_to_camel'(下划线转小驼峰)、'camel_to_snake'(小驼峰转下划线),默认为'no_case'
|
||||||
|
:return: 字典结果
|
||||||
|
"""
|
||||||
|
if isinstance(obj, Base):
|
||||||
|
base_dict = obj.__dict__.copy()
|
||||||
|
base_dict.pop('_sa_instance_state', None)
|
||||||
|
for name, value in base_dict.items():
|
||||||
|
if isinstance(value, InstrumentedList):
|
||||||
|
base_dict[name] = cls.serialize_result(value, 'snake_to_camel')
|
||||||
|
elif isinstance(obj, dict):
|
||||||
|
base_dict = obj.copy()
|
||||||
|
if transform_case == 'snake_to_camel':
|
||||||
|
return {CamelCaseUtil.snake_to_camel(k): v for k, v in base_dict.items()}
|
||||||
|
elif transform_case == 'camel_to_snake':
|
||||||
|
return {SnakeCaseUtil.camel_to_snake(k): v for k, v in base_dict.items()}
|
||||||
|
|
||||||
|
return base_dict
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def serialize_result(
|
||||||
|
cls, result: Any, transform_case: Literal['no_case', 'snake_to_camel', 'camel_to_snake'] = 'no_case'
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
将sqlalchemy查询结果序列化
|
||||||
|
|
||||||
|
:param result: sqlalchemy查询结果
|
||||||
|
:param transform_case: 转换得到的结果形式,可选的有'no_case'(不转换)、'snake_to_camel'(下划线转小驼峰)、'camel_to_snake'(小驼峰转下划线),默认为'no_case'
|
||||||
|
:return: 序列化结果
|
||||||
|
"""
|
||||||
|
if isinstance(result, (Base, dict)):
|
||||||
|
return cls.base_to_dict(result, transform_case)
|
||||||
|
elif isinstance(result, list):
|
||||||
|
return [cls.serialize_result(row, transform_case) for row in result]
|
||||||
|
elif isinstance(result, Row):
|
||||||
|
if all([isinstance(row, Base) for row in result]):
|
||||||
|
return [cls.base_to_dict(row, transform_case) for row in result]
|
||||||
|
elif any([isinstance(row, Base) for row in result]):
|
||||||
|
return [cls.serialize_result(row, transform_case) for row in result]
|
||||||
|
else:
|
||||||
|
result_dict = result._asdict()
|
||||||
|
if transform_case == 'snake_to_camel':
|
||||||
|
return {CamelCaseUtil.snake_to_camel(k): v for k, v in result_dict.items()}
|
||||||
|
elif transform_case == 'camel_to_snake':
|
||||||
|
return {SnakeCaseUtil.camel_to_snake(k): v for k, v in result_dict.items()}
|
||||||
|
return result_dict
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class CamelCaseUtil:
|
||||||
|
"""
|
||||||
|
下划线形式(snake_case)转小驼峰形式(camelCase)工具方法
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def snake_to_camel(cls, snake_str: str):
|
||||||
|
"""
|
||||||
|
下划线形式字符串(snake_case)转换为小驼峰形式字符串(camelCase)
|
||||||
|
|
||||||
|
:param snake_str: 下划线形式字符串
|
||||||
|
:return: 小驼峰形式字符串
|
||||||
|
"""
|
||||||
|
# 分割字符串
|
||||||
|
words = snake_str.split('_')
|
||||||
|
# 小驼峰命名,第一个词首字母小写,其余词首字母大写
|
||||||
|
return words[0] + ''.join(word.capitalize() for word in words[1:])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def transform_result(cls, result: Any):
|
||||||
|
"""
|
||||||
|
针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法
|
||||||
|
|
||||||
|
:param result: 输入数据
|
||||||
|
:return: 小驼峰形式结果
|
||||||
|
"""
|
||||||
|
return SqlalchemyUtil.serialize_result(result=result, transform_case='snake_to_camel')
|
||||||
|
|
||||||
|
|
||||||
|
class SnakeCaseUtil:
|
||||||
|
"""
|
||||||
|
小驼峰形式(camelCase)转下划线形式(snake_case)工具方法
|
||||||
|
"""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def camel_to_snake(cls, camel_str: str):
|
||||||
|
"""
|
||||||
|
小驼峰形式字符串(camelCase)转换为下划线形式字符串(snake_case)
|
||||||
|
|
||||||
|
:param camel_str: 小驼峰形式字符串
|
||||||
|
:return: 下划线形式字符串
|
||||||
|
"""
|
||||||
|
# 在大写字母前添加一个下划线,然后将整个字符串转为小写
|
||||||
|
words = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', camel_str)
|
||||||
|
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', words).lower()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def transform_result(cls, result: Any):
|
||||||
|
"""
|
||||||
|
针对不同类型将下划线形式(snake_case)批量转换为小驼峰形式(camelCase)方法
|
||||||
|
|
||||||
|
:param result: 输入数据
|
||||||
|
:return: 小驼峰形式结果
|
||||||
|
"""
|
||||||
|
return SqlalchemyUtil.serialize_result(result=result, transform_case='camel_to_snake')
|
||||||
|
|
||||||
|
|
||||||
|
def export_list2excel(list_data: List):
|
||||||
|
"""
|
||||||
|
工具方法:将需要导出的list数据转化为对应excel的二进制数据
|
||||||
|
|
||||||
|
:param list_data: 数据列表
|
||||||
|
:return: 字典信息对应excel的二进制数据
|
||||||
|
"""
|
||||||
|
df = pd.DataFrame(list_data)
|
||||||
|
binary_data = io.BytesIO()
|
||||||
|
df.to_excel(binary_data, index=False, engine='openpyxl')
|
||||||
|
binary_data = binary_data.getvalue()
|
||||||
|
|
||||||
|
return binary_data
|
||||||
|
|
||||||
|
|
||||||
|
def get_excel_template(header_list: List, selector_header_list: List, option_list: List[dict]):
|
||||||
|
"""
|
||||||
|
工具方法:将需要导出的list数据转化为对应excel的二进制数据
|
||||||
|
|
||||||
|
:param header_list: 表头数据列表
|
||||||
|
:param selector_header_list: 需要设置为选择器格式的表头数据列表
|
||||||
|
:param option_list: 选择器格式的表头预设的选项列表
|
||||||
|
:return: 模板excel的二进制数据
|
||||||
|
"""
|
||||||
|
# 创建Excel工作簿
|
||||||
|
wb = Workbook()
|
||||||
|
# 选择默认的活动工作表
|
||||||
|
ws = wb.active
|
||||||
|
|
||||||
|
# 设置表头文字
|
||||||
|
headers = header_list
|
||||||
|
|
||||||
|
# 设置表头背景样式为灰色,前景色为白色
|
||||||
|
header_fill = PatternFill(start_color='ababab', end_color='ababab', fill_type='solid')
|
||||||
|
|
||||||
|
# 将表头写入第一行
|
||||||
|
for col_num, header in enumerate(headers, 1):
|
||||||
|
cell = ws.cell(row=1, column=col_num)
|
||||||
|
cell.value = header
|
||||||
|
cell.fill = header_fill
|
||||||
|
# 设置列宽度为16
|
||||||
|
ws.column_dimensions[chr(64 + col_num)].width = 12
|
||||||
|
# 设置水平居中对齐
|
||||||
|
cell.alignment = Alignment(horizontal='center')
|
||||||
|
|
||||||
|
# 设置选择器的预设选项
|
||||||
|
options = option_list
|
||||||
|
|
||||||
|
# 获取selector_header的字母索引
|
||||||
|
for selector_header in selector_header_list:
|
||||||
|
column_selector_header_index = headers.index(selector_header) + 1
|
||||||
|
|
||||||
|
# 创建数据有效性规则
|
||||||
|
header_option = []
|
||||||
|
for option in options:
|
||||||
|
if option.get(selector_header):
|
||||||
|
header_option = option.get(selector_header)
|
||||||
|
dv = DataValidation(type='list', formula1=f'"{",".join(header_option)}"')
|
||||||
|
# 设置数据有效性规则的起始单元格和结束单元格
|
||||||
|
dv.add(
|
||||||
|
f'{get_column_letter(column_selector_header_index)}2:{get_column_letter(column_selector_header_index)}1048576'
|
||||||
|
)
|
||||||
|
# 添加数据有效性规则到工作表
|
||||||
|
ws.add_data_validation(dv)
|
||||||
|
|
||||||
|
# 保存Excel文件为字节类型的数据
|
||||||
|
file = io.BytesIO()
|
||||||
|
wb.save(file)
|
||||||
|
file.seek(0)
|
||||||
|
|
||||||
|
# 读取字节数据
|
||||||
|
excel_data = file.getvalue()
|
||||||
|
|
||||||
|
return excel_data
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from module_gen.constants.gen_constants import GenConstants
|
from app.common.constant import GenConstants
|
||||||
|
|
||||||
|
|
||||||
def snake_to_pascal_case(value):
|
def snake_to_pascal_case(value):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from module_gen.utils.jinja2_tools import snake_to_pascal_case, snake_to_camel, snake_2_colon, is_base_column, \
|
from .jinja2_tools import snake_to_pascal_case, snake_to_camel, snake_2_colon, is_base_column, \
|
||||||
get_sqlalchemy_type, get_column_options
|
get_sqlalchemy_type, get_column_options
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,576 @@
|
|||||||
|
<!-- 演示示例 -->
|
||||||
|
<template>
|
||||||
|
<div class="app-container">
|
||||||
|
<!-- 搜索区域 -->
|
||||||
|
<div class="search-container">
|
||||||
|
<el-form ref="queryFormRef" :model="queryFormData" :inline="true" label-suffix=":">
|
||||||
|
<el-form-item prop="name" label="名称">
|
||||||
|
<el-input v-model="queryFormData.name" placeholder="请输入名称" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item prop="status" label="状态">
|
||||||
|
<el-select v-model="queryFormData.status" placeholder="请选择状态" style="width: 167.5px" clearable>
|
||||||
|
<el-option value="true" label="启用" />
|
||||||
|
<el-option value="false" label="停用" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 时间范围,收起状态下隐藏 -->
|
||||||
|
<el-form-item v-if="isExpand" prop="start_time" label="创建时间">
|
||||||
|
<DatePicker
|
||||||
|
v-model="dateRange"
|
||||||
|
@update:model-value="handleDateRangeChange"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<!-- 查询、重置、展开/收起按钮 -->
|
||||||
|
<el-form-item class="search-buttons">
|
||||||
|
<el-button type="primary" icon="search" @click="handleQuery">
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<el-button 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 shadow="hover" class="data-table">
|
||||||
|
<template #header>
|
||||||
|
<div class="card-header">
|
||||||
|
<span>
|
||||||
|
<el-tooltip content="流程列表">
|
||||||
|
<QuestionFilled class="w-4 h-4 mx-1" />
|
||||||
|
</el-tooltip>
|
||||||
|
演示示例列表
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 功能区域 -->
|
||||||
|
<div class="data-table__toolbar">
|
||||||
|
<div class="data-table__toolbar--actions">
|
||||||
|
<el-button type="success" icon="plus" @click="handleOpenDialog('create')">新增</el-button>
|
||||||
|
<el-button type="danger" icon="delete" :disabled="selectIds.length === 0"
|
||||||
|
@click="handleDelete(selectIds)">批量删除</el-button>
|
||||||
|
<el-dropdown trigger="click">
|
||||||
|
<el-button type="default" :disabled="selectIds.length === 0" icon="ArrowDown">更多</el-button>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item icon="Check" @click="handleMoreClick(true)">批量启用</el-dropdown-item>
|
||||||
|
<el-dropdown-item icon="CircleClose"
|
||||||
|
@click="handleMoreClick(false)">批量停用</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</div>
|
||||||
|
<div class="data-table__toolbar--tools">
|
||||||
|
<el-tooltip content="导入">
|
||||||
|
<el-button type="info" icon="upload" circle @click="handleOpenImportDialog" />
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="导出">
|
||||||
|
<el-button type="warning" icon="download" circle @click="handleExport" />
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="刷新">
|
||||||
|
<el-button type="primary" icon="refresh" circle @click="handleRefresh" />
|
||||||
|
</el-tooltip>
|
||||||
|
<el-tooltip content="列表筛选">
|
||||||
|
<el-dropdown trigger="click">
|
||||||
|
<el-button type="default" icon="operation" circle />
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item v-for="column in tableColumns" :key="column.prop"
|
||||||
|
:command="column">
|
||||||
|
<el-checkbox v-model="column.show">
|
||||||
|
{{ column.label }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表格区域:系统配置列表 -->
|
||||||
|
<el-table ref="dataTableRef" v-loading="loading" :data="pageTableData" highlight-current-row
|
||||||
|
class="data-table__content" height="450" border stripe @selection-change="handleSelectionChange">
|
||||||
|
<template #empty>
|
||||||
|
<el-empty :image-size="80" description="暂无数据" />
|
||||||
|
</template>
|
||||||
|
<el-table-column v-if="tableColumns.find(col => col.prop === 'selection')?.show" type="selection"
|
||||||
|
min-width="55" align="center" />
|
||||||
|
<el-table-column v-if="tableColumns.find(col => col.prop === 'index')?.show" fixed label="序号"
|
||||||
|
min-width="60">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ (queryFormData.page_no - 1) * queryFormData.page_size + scope.$index + 1 }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column v-if="tableColumns.find(col => col.prop === 'name')?.show" label="名称"
|
||||||
|
prop="name" min-width="140" />
|
||||||
|
<el-table-column v-if="tableColumns.find(col => col.prop === 'status')?.show" label="状态" prop="status"
|
||||||
|
min-width="80">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="scope.row.status === true ? 'success' : 'danger'">
|
||||||
|
{{ scope.row.status === true ? "启用" : "停用" }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column v-if="tableColumns.find(col => col.prop === 'description')?.show" label="描述"
|
||||||
|
prop="description" min-width="140" />
|
||||||
|
<el-table-column v-if="tableColumns.find(col => col.prop === 'created_at')?.show" label="创建时间"
|
||||||
|
prop="created_at" min-width="180" sortable />
|
||||||
|
<el-table-column v-if="tableColumns.find(col => col.prop === 'updated_at')?.show" label="更新时间"
|
||||||
|
prop="updated_at" min-width="180" sortable />
|
||||||
|
<el-table-column v-if="tableColumns.find(col => col.prop === 'creator')?.show" key="creator" label="创建人"
|
||||||
|
min-width="100">
|
||||||
|
<template #default="scope">
|
||||||
|
{{ scope.row.creator?.name }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
|
||||||
|
<el-table-column v-if="tableColumns.find(col => col.prop === 'operation')?.show" fixed="right"
|
||||||
|
label="操作" align="center" min-width="200">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button type="info" size="small" link icon="document"
|
||||||
|
@click="handleOpenDialog('detail', scope.row.id)">详情</el-button>
|
||||||
|
<el-button type="primary" size="small" link icon="edit"
|
||||||
|
@click="handleOpenDialog('update', scope.row.id)">编辑</el-button>
|
||||||
|
<el-button type="danger" size="small" link icon="delete"
|
||||||
|
@click="handleDelete([scope.row.id])">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<!-- 分页区域 -->
|
||||||
|
<template #footer>
|
||||||
|
<pagination v-model:total="total" v-model:page="queryFormData.page_no"
|
||||||
|
v-model:limit="queryFormData.page_size" @pagination="loadingData" />
|
||||||
|
</template>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<!-- 弹窗区域 -->
|
||||||
|
<el-dialog v-model="dialogVisible.visible" :title="dialogVisible.title" @close="handleCloseDialog">
|
||||||
|
<!-- 详情 -->
|
||||||
|
<template v-if="dialogVisible.type === 'detail'">
|
||||||
|
<el-descriptions :column="4" border>
|
||||||
|
<el-descriptions-item label="名称" :span="2">
|
||||||
|
{{ detailFormData.name }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="状态" :span="2">
|
||||||
|
<el-tag :type="detailFormData.status ? 'success' : 'danger'">
|
||||||
|
{{ detailFormData.status ? '启用' : '停用' }}
|
||||||
|
</el-tag>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="描述" :span="2">
|
||||||
|
{{ detailFormData.description }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="创建人" :span="2">
|
||||||
|
{{ detailFormData.creator?.name }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="创建时间" :span="2">
|
||||||
|
{{ detailFormData.created_at }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="更新时间" :span="2">
|
||||||
|
{{ detailFormData.updated_at }}
|
||||||
|
</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</template>
|
||||||
|
<!-- 新增、编辑表单 -->
|
||||||
|
<template v-else>
|
||||||
|
<el-form ref="dataFormRef" :model="formData" :rules="rules" label-suffix=":" label-width="auto"
|
||||||
|
label-position="right">
|
||||||
|
<el-form-item label="名称" prop="name">
|
||||||
|
<el-input v-model="formData.name" placeholder="请输入名称" :maxlength="50" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-radio-group v-model="formData.status">
|
||||||
|
<el-radio :value="true">
|
||||||
|
启用
|
||||||
|
</el-radio>
|
||||||
|
<el-radio :value="false">
|
||||||
|
停用
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="描述" prop="description">
|
||||||
|
<el-input v-model="formData.description" :rows="4" :maxlength="100" show-word-limit
|
||||||
|
type="textarea" placeholder="请输入描述" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<!-- 详情弹窗不需要确定按钮的提交逻辑 -->
|
||||||
|
<el-button @click="handleCloseDialog">取消</el-button>
|
||||||
|
<el-button v-if="dialogVisible.type !== 'detail'" type="primary"
|
||||||
|
@click="handleSubmit">确定</el-button>
|
||||||
|
<el-button v-else type="primary" @click="handleCloseDialog">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 用户导入 -->
|
||||||
|
<ImportModal v-model="importDialogVisible" title="导入数据" @import-success="handleQuery()" @download-template="handleDownloadTemplate" @upload="handleUpload" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
defineOptions({
|
||||||
|
name: "Example",
|
||||||
|
inheritAttrs: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
import { ref, reactive, onMounted } from "vue";
|
||||||
|
import { ElMessage } from "element-plus";
|
||||||
|
import { ResultEnum } from "@/enums/api/result.enum";
|
||||||
|
import ExampleAPI, { ExampleTable, ExampleForm, ExamplePageQuery } from "@/api/demo/example";
|
||||||
|
import ImportModal from "@/components/Upload/ImportModal.vue";
|
||||||
|
import DatePicker from "@/components/DatePicker/index.vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(['import-success']);
|
||||||
|
|
||||||
|
const queryFormRef = ref();
|
||||||
|
const dataFormRef = ref();
|
||||||
|
const total = ref(0);
|
||||||
|
const selectIds = ref<number[]>([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const isExpand = ref(false);
|
||||||
|
const isExpandable = ref(true);
|
||||||
|
|
||||||
|
// 分页表单
|
||||||
|
const pageTableData = ref<ExampleTable[]>([]);
|
||||||
|
|
||||||
|
// 表格列配置
|
||||||
|
const tableColumns = ref([
|
||||||
|
{ prop: 'selection', label: '选择框', show: true },
|
||||||
|
{ prop: 'index', label: '序号', show: true },
|
||||||
|
{ prop: 'name', label: '名称', show: true },
|
||||||
|
{ prop: 'status', label: '状态', show: true },
|
||||||
|
{ prop: 'description', label: '描述', show: true },
|
||||||
|
{ prop: 'created_at', label: '创建时间', show: true },
|
||||||
|
{ prop: 'updated_at', label: '更新时间', show: true },
|
||||||
|
{ prop: 'creator', label: '创建人', show: true },
|
||||||
|
{ prop: 'operation', label: '操作', show: true }
|
||||||
|
])
|
||||||
|
|
||||||
|
// 详情表单
|
||||||
|
const detailFormData = ref<ExampleTable>({});
|
||||||
|
|
||||||
|
// 日期范围临时变量
|
||||||
|
const dateRange = ref<[Date, Date] | []>([]);
|
||||||
|
|
||||||
|
// 处理日期范围变化
|
||||||
|
function handleDateRangeChange(range: [Date, Date]) {
|
||||||
|
dateRange.value = range;
|
||||||
|
if (range && range.length === 2) {
|
||||||
|
queryFormData.start_time = range[0].toISOString();
|
||||||
|
queryFormData.end_time = range[1].toISOString();
|
||||||
|
} else {
|
||||||
|
queryFormData.start_time = undefined;
|
||||||
|
queryFormData.end_time = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分页查询参数
|
||||||
|
const queryFormData = reactive<ExamplePageQuery>({
|
||||||
|
page_no: 1,
|
||||||
|
page_size: 10,
|
||||||
|
name: undefined,
|
||||||
|
status: undefined,
|
||||||
|
start_time: undefined,
|
||||||
|
end_time: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 编辑表单
|
||||||
|
const formData = reactive<ExampleForm>({
|
||||||
|
id: undefined,
|
||||||
|
name: '',
|
||||||
|
status: true,
|
||||||
|
description: undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
// 弹窗状态
|
||||||
|
const dialogVisible = reactive({
|
||||||
|
title: "",
|
||||||
|
visible: false,
|
||||||
|
type: 'create' as 'create' | 'update' | 'detail',
|
||||||
|
});
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const rules = reactive({
|
||||||
|
name: [{ required: true, message: "请输入名称", trigger: "blur" }],
|
||||||
|
status: [{ required: true, message: "请选择状态", trigger: "blur" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 导入弹窗显示状态
|
||||||
|
const importDialogVisible = ref(false);
|
||||||
|
|
||||||
|
// 列表刷新
|
||||||
|
async function handleRefresh() {
|
||||||
|
await loadingData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加载表格数据
|
||||||
|
async function loadingData() {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await ExampleAPI.getExampleList(queryFormData);
|
||||||
|
pageTableData.value = response.data.data.items;
|
||||||
|
total.value = response.data.data.total;
|
||||||
|
}
|
||||||
|
catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询(重置页码后获取数据)
|
||||||
|
async function handleQuery() {
|
||||||
|
queryFormData.page_no = 1;
|
||||||
|
loadingData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置查询
|
||||||
|
async function handleResetQuery() {
|
||||||
|
queryFormRef.value.resetFields();
|
||||||
|
queryFormData.page_no = 1;
|
||||||
|
// 重置日期范围选择器
|
||||||
|
dateRange.value = [];
|
||||||
|
queryFormData.start_time = undefined;
|
||||||
|
queryFormData.end_time = undefined;
|
||||||
|
loadingData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义初始表单数据常量
|
||||||
|
const initialFormData: ExampleForm = {
|
||||||
|
id: undefined,
|
||||||
|
name: '',
|
||||||
|
status: true,
|
||||||
|
description: '',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置表单
|
||||||
|
async function resetForm() {
|
||||||
|
if (dataFormRef.value) {
|
||||||
|
dataFormRef.value.resetFields();
|
||||||
|
dataFormRef.value.clearValidate();
|
||||||
|
}
|
||||||
|
// 完全重置 formData 为初始状态
|
||||||
|
Object.assign(formData, initialFormData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 行复选框选中项变化
|
||||||
|
async function handleSelectionChange(selection: any) {
|
||||||
|
selectIds.value = selection.map((item: any) => item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
async function handleCloseDialog() {
|
||||||
|
dialogVisible.visible = false;
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打开弹窗
|
||||||
|
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
|
||||||
|
dialogVisible.type = type;
|
||||||
|
if (id) {
|
||||||
|
const response = await ExampleAPI.getExampleDetail(id);
|
||||||
|
if (type === 'detail') {
|
||||||
|
dialogVisible.title = "详情";
|
||||||
|
Object.assign(detailFormData.value, response.data.data);
|
||||||
|
} else if (type === 'update') {
|
||||||
|
dialogVisible.title = "修改";
|
||||||
|
Object.assign(formData, response.data.data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dialogVisible.title = "新增公告通知";
|
||||||
|
formData.id = undefined;
|
||||||
|
}
|
||||||
|
dialogVisible.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单(防抖)
|
||||||
|
async function handleSubmit() {
|
||||||
|
// 表单校验
|
||||||
|
dataFormRef.value.validate(async (valid: any) => {
|
||||||
|
if (valid) {
|
||||||
|
loading.value = true;
|
||||||
|
// 根据弹窗传入的参数(deatil\create\update)判断走什么逻辑
|
||||||
|
const id = formData.id;
|
||||||
|
if (id) {
|
||||||
|
try {
|
||||||
|
await ExampleAPI.updateExample(id, { id, ...formData })
|
||||||
|
dialogVisible.visible = false;
|
||||||
|
resetForm();
|
||||||
|
handleCloseDialog();
|
||||||
|
handleResetQuery();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await ExampleAPI.createExample(formData)
|
||||||
|
dialogVisible.visible = false;
|
||||||
|
resetForm();
|
||||||
|
handleCloseDialog();
|
||||||
|
handleResetQuery();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除、批量删除
|
||||||
|
async function handleDelete(ids: number[]) {
|
||||||
|
ElMessageBox.confirm("确认删除该项数据?", "警告", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
await ExampleAPI.deleteExample(ids);
|
||||||
|
handleResetQuery();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessageBox.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出
|
||||||
|
async function handleExport() {
|
||||||
|
ElMessageBox.confirm('是否确认导出当前系统配置?', '警告', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(async () => {
|
||||||
|
let downloadUrl = '';
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
|
||||||
|
const response = await ExampleAPI.exportExample(queryFormData);
|
||||||
|
const fileData = response.data;
|
||||||
|
const fileName = decodeURI(response.headers["content-disposition"].split(";")[1].split("=")[1]);
|
||||||
|
const fileType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
|
||||||
|
|
||||||
|
const blob = new Blob([fileData], { type: fileType });
|
||||||
|
downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const downloadLink = document.createElement("a");
|
||||||
|
downloadLink.href = downloadUrl;
|
||||||
|
downloadLink.download = fileName;
|
||||||
|
|
||||||
|
document.body.appendChild(downloadLink);
|
||||||
|
downloadLink.click();
|
||||||
|
|
||||||
|
document.body.removeChild(downloadLink);
|
||||||
|
} catch (error: any) {
|
||||||
|
// 错误信息已经在响应拦截器中处理并显示
|
||||||
|
console.error('导出失败:', error);
|
||||||
|
} finally {
|
||||||
|
if (downloadUrl) {
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessageBox.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理上传
|
||||||
|
const handleUpload = async (formData: FormData, file: File) => {
|
||||||
|
try {
|
||||||
|
const response = await ExampleAPI.importExample(formData);
|
||||||
|
if (response.data.code === ResultEnum.SUCCESS) {
|
||||||
|
ElMessage.success(`${response.data.msg},${response.data.data}`);
|
||||||
|
emit('import-success');
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下载导入模板
|
||||||
|
const handleDownloadTemplate = () => {
|
||||||
|
ExampleAPI.downloadTemplate().then((response: any) => {
|
||||||
|
const fileData = response.data;
|
||||||
|
const fileName = decodeURI(response.headers['content-disposition'].split('; ')[1].split('=')[1]);
|
||||||
|
const fileType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8';
|
||||||
|
|
||||||
|
const blob = new Blob([fileData], { type: fileType });
|
||||||
|
const downloadUrl = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const downloadLink = document.createElement('a');
|
||||||
|
downloadLink.href = downloadUrl;
|
||||||
|
downloadLink.download = fileName;
|
||||||
|
|
||||||
|
document.body.appendChild(downloadLink);
|
||||||
|
downloadLink.click();
|
||||||
|
|
||||||
|
document.body.removeChild(downloadLink);
|
||||||
|
window.URL.revokeObjectURL(downloadUrl);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开导入弹窗
|
||||||
|
function handleOpenImportDialog() {
|
||||||
|
importDialogVisible.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量启用/停用
|
||||||
|
async function handleMoreClick(status: boolean) {
|
||||||
|
if (selectIds.value.length) {
|
||||||
|
ElMessageBox.confirm(`确认${status ? '启用' : '停用'}该项数据?`, "警告", {
|
||||||
|
confirmButtonText: "确定",
|
||||||
|
cancelButtonText: "取消",
|
||||||
|
type: "warning",
|
||||||
|
}).then(async () => {
|
||||||
|
try {
|
||||||
|
loading.value = true;
|
||||||
|
await ExampleAPI.batchAvailableExample({ ids: selectIds.value, status });
|
||||||
|
handleResetQuery();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessageBox.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadingData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped></style>
|
||||||
Reference in New Issue
Block a user