refactor(module): 重构模块路径和数据库配置

- 将前端API路径从`system`和`monitor`重命名为`module_system`和`module_monitor`
- 移除MongoDB相关配置和依赖
- 统一数据库类型命名为`mysql`和`postgres`
- 修复代码生成模板中的字段命名问题
- 更新依赖项并移除不必要的包
- 优化数据库连接配置
- 添加新的文档和Redoc视图组件
- 修复SQL脚本中的字段注释
This commit is contained in:
zhangtao
2025-11-10 01:43:23 +08:00
parent bbde7b8c6e
commit 3df4f5a45a
89 changed files with 964 additions and 1531 deletions
+3
View File
@@ -9,6 +9,9 @@ from app.utils.import_util import ImportUtil
from app.core.base_model import MappedBase
from app.config.setting import settings
# 确保 alembic 版本目录存在
settings.ALEMBIC_VERSION_DIR.mkdir(parents=True, exist_ok=True)
# 清除MappedBase.metadata中的表定义,避免重复注册
if hasattr(MappedBase, 'metadata') and MappedBase.metadata.tables:
print(f"🧹 清除已存在的表定义,当前有 {len(MappedBase.metadata.tables)} 个表")
@@ -1,21 +1,20 @@
# -*- coding:utf-8 -*-
from typing import List
from fastapi import APIRouter, Depends, Query, Body, Path
from fastapi import APIRouter, Depends, Body, Path
from fastapi.responses import JSONResponse
from app.common.response import SuccessResponse, ErrorResponse, StreamResponse
from app.common.response import SuccessResponse, StreamResponse
from app.core.dependencies import AuthPermission
from app.core.router_class import OperationLogRoute
from app.core.base_params import PaginationQueryParam
from app.common.request import PaginationService
from app.api.v1.module_system.auth.schema import AuthSchema
from app.common.constant import RET
from .param import GenTableQueryParam
from .schema import GenTableSchema, GenTableOutSchema
from .service import GenTableColumnService, GenTableService
from app.utils.common_util import bytes2file_response
from app.core.logger import logger
from .param import GenTableQueryParam
from .schema import GenTableSchema
from .service import GenTableService
GenRouter = APIRouter(route_class=OperationLogRoute, prefix='/gencode', tags=["代码生成模块"])
@@ -110,7 +109,7 @@ async def gen_table_detail_controller(
@GenRouter.post("/create", summary="创建表结构", description="创建表结构")
async def create_table_controller(
sql: str = Body(..., description="SQL语句CREATE TABLE user_demo (\n id INTEGER NOT NULL PRIMARY KEY,\n username VARCHAR(64) NOT NULL UNIQUE,\n);"),
sql: str = Body(..., description="SQL语句,用于创建表结构"),
auth: AuthSchema = Depends(AuthPermission(["generator:gencode:create"])),
) -> JSONResponse:
"""
@@ -3,16 +3,16 @@
from sqlalchemy.engine.row import Row
from sqlalchemy import and_, select, text
from typing import List, Optional, Sequence, Dict, Union, Any
from sqlglot.expressions import Expression
from app.core.logger import logger
from app.config.setting import settings
from app.core.base_crud import CRUDBase
from app.api.v1.module_system.auth.schema import AuthSchema
from .param import GenTableQueryParam, GenTableColumnQueryParam
from .param import GenTableQueryParam
from .model import GenTableModel, GenTableColumnModel
from .schema import (
GenTableSchema,
GenTableOutSchema,
GenTableColumnSchema,
GenTableColumnOutSchema,
GenDBTableSchema,
@@ -80,7 +80,6 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
返回:
- Sequence[GenTableModel]: 业务表列表信息。
"""
# 使用基础CRUD的list与like检索
return await self.list(search=search.__dict__, order_by=[{"created_at": "desc"}], preload=preload)
async def add_gen_table(self, add_model: GenTableSchema) -> GenTableModel:
@@ -93,9 +92,9 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
返回:
- GenTableModel: 新增的业务表信息对象。
"""
return await self.create(add_model.model_dump(exclude_unset=True, exclude={"sub", "tree", "crud"}))
return await self.create(data=add_model)
async def edit_gen_table(self, table_id: int, edit_model: GenTableSchema) -> GenTableSchema:
async def edit_gen_table(self, table_id: int, edit_model: GenTableSchema) -> GenTableModel:
"""
修改业务表信息。
@@ -107,9 +106,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
- GenTableSchema: 修改后的业务表信息模型。
"""
# 排除嵌套对象字段,避免SQLAlchemy尝试直接将字典设置到模型实例上
data_dict = edit_model.model_dump(exclude_unset=True, exclude={"columns", "pk_column", "sub_table", "sub"})
obj = await self.update(id=table_id, data=data_dict)
return GenTableSchema.model_validate(obj)
return await self.update(id=table_id, data=edit_model.model_dump(exclude_unset=True, exclude={"columns"}))
async def delete_gen_table(self, ids: List[int]) -> None:
"""
@@ -132,8 +129,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
"""
# 使用更健壮的方式检测数据库方言
if settings.DATABASE_TYPE == "postgresql":
# 修复:PostgreSQL不提供table_comment,使用pg_catalog获取注释
if settings.DATABASE_TYPE == "postgres":
query_sql = (
select(
text("t.table_catalog as database_name"),
@@ -155,7 +151,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
)
)
)
elif settings.DATABASE_TYPE == "mysql":
else:
query_sql = (
select(
text("table_schema as database_name"),
@@ -170,38 +166,19 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
)
)
)
else:
query_sql = (
select(
text("'fastapiadmin' as database_name"), # SQLite没有数据库名概念,设为空字符串
text("name as table_name"),
text("type as table_type"),
text("name as table_comment"), # SQLite中使用name作为表名和注释
)
.select_from(text("sqlite_master"))
.where(
and_(
text("type = 'table'"),
)
)
)
# 动态条件构造
params = {}
if search and search.table_name:
if settings.DATABASE_TYPE == "sqlite":
query_sql = query_sql.where(
text("lower(name) like lower(:table_name)")
)
else:
query_sql = query_sql.where(
text("lower(table_name) like lower(:table_name)")
)
params['table_name'] = f"%{search.table_name}%"
if search and search.table_comment:
if settings.DATABASE_TYPE == "sqlite":
# 对于PostgreSQL,表注释字段是pd.description,而不是table_comment
if settings.DATABASE_TYPE == "postgres":
query_sql = query_sql.where(
text("lower(name) like lower(:table_comment)")
text("lower(pd.description) like lower(:table_comment)")
)
else:
query_sql = query_sql.where(
@@ -235,78 +212,58 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
返回:
- list[GenDBTableSchema]: 数据库表信息对象列表。
"""
# 使用更健壮的方式检测数据库方言
if settings.DATABASE_TYPE == "postgresql":
# 修复:PostgreSQL不提供table_comment,使用pg_catalog获取注释
query_sql = (
select(
text("t.table_catalog as database_name"),
text("t.table_name as table_name"),
text("t.table_type as table_type"),
text("pd.description as table_comment"),
)
.select_from(text(
"information_schema.tables t \n"
"LEFT JOIN pg_catalog.pg_class c ON c.relname = t.table_name \n"
"LEFT JOIN pg_catalog.pg_namespace n ON n.nspname = t.table_schema AND c.relnamespace = n.oid \n"
"LEFT JOIN pg_catalog.pg_description pd ON pd.objoid = c.oid AND pd.objsubid = 0"
))
.where(
and_(
text("t.table_catalog = (select current_database())"),
text("t.is_insertable_into = 'YES'"),
text("t.table_schema = 'public'"),
)
)
)
elif settings.DATABASE_TYPE == "mysql":
query_sql = (
select(
text("table_schema as database_name"),
text("table_name as table_name"),
text("table_type as table_type"),
text("table_comment as table_comment"),
)
.select_from(text("information_schema.tables"))
.where(
and_(
text("table_schema = (select database())"),
)
)
)
else:
query_sql = (
select(
text("'fastapiadmin' as database_name"), # SQLite没有数据库名概念,设为空字符串
text("name as table_name"),
text("type as table_type"),
text("name as table_comment"), # SQLite中使用name作为表名和注释
)
.select_from(text("sqlite_master"))
.where(
and_(
text("type = 'table'"),
)
)
)
# 处理空列表情况
if not table_names:
return []
table_names_str = "','".join(table_names)
# 修复SQL查询中的参数绑定问题
if table_names:
if settings.DATABASE_TYPE == "sqlite":
# 对于SQLite,我们直接在SQL中使用表名,因为参数绑定有问题
query_sql = query_sql.where(
text(f"name IN ('{table_names_str}')")
)
gen_db_table_list = (await self.db.execute(query_sql)).fetchall()
# 使用更健壮的方式检测数据库方言
if settings.DATABASE_TYPE == "postgres":
# PostgreSQL使用ANY操作符和正确的参数绑定
query_sql = """
SELECT
t.table_catalog as database_name,
t.table_name as table_name,
t.table_type as table_type,
pd.description as table_comment
FROM
information_schema.tables t
LEFT JOIN pg_catalog.pg_class c ON c.relname = t.table_name
LEFT JOIN pg_catalog.pg_namespace n ON n.nspname = t.table_schema AND c.relnamespace = n.oid
LEFT JOIN pg_catalog.pg_description pd ON pd.objoid = c.oid AND pd.objsubid = 0
WHERE
t.table_catalog = (select current_database())
AND t.is_insertable_into = 'YES'
AND t.table_schema = 'public'
AND t.table_name = ANY(:table_names)
"""
else:
# MySQL和PostgreSQL使用IN拼接(注意已在上方限定schema范围)
query_sql = query_sql.where(
text(f"table_name IN ('{table_names_str}')")
)
gen_db_table_list = (await self.db.execute(query_sql)).fetchall()
query_sql = """
SELECT
table_schema as database_name,
table_name as table_name,
table_type as table_type,
table_comment as table_comment
FROM
information_schema.tables
WHERE
table_schema = (select database())
AND table_name IN :table_names
"""
# 创建新的数据库会话上下文来执行查询,避免受外部事务状态影响
try:
# 去重表名列表,避免重复查询
unique_table_names = list(set(table_names))
# 使用只读事务执行查询,不影响主事务
if settings.DATABASE_TYPE == "postgres":
gen_db_table_list = (await self.db.execute(text(query_sql), {"table_names": unique_table_names})).fetchall()
else:
gen_db_table_list = (await self.db.execute(query_sql)).fetchall()
gen_db_table_list = (await self.db.execute(text(query_sql), {"table_names": tuple(unique_table_names)})).fetchall()
except Exception as e:
logger.error(f"查询表信息时发生错误: {e}")
# 查询错误时直接抛出,不需要事务处理
raise
# 将Row对象转换为字典列表,解决JSON序列化问题
dict_data = []
@@ -335,13 +292,8 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
# 根据不同数据库类型使用不同的查询方式
if settings.DATABASE_TYPE.lower() == 'mysql':
query = text("SELECT 1 FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = :table_name")
elif settings.DATABASE_TYPE.lower() == 'postgresql':
query = text("SELECT 1 FROM pg_tables WHERE tablename = :table_name")
elif settings.DATABASE_TYPE.lower() == 'sqlite':
query = text("SELECT name FROM sqlite_master WHERE type='table' AND name = :table_name")
else:
# 默认查询方式
query = text("SELECT 1 FROM information_schema.tables WHERE table_name = :table_name")
query = text("SELECT 1 FROM pg_tables WHERE tablename = :table_name")
result = await self.db.execute(query, {"table_name": table_name})
return result.scalar() is not None
@@ -350,7 +302,7 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
# 出错时返回False,避免误报表已存在
return False
async def create_table_by_sql(self, sql: str) -> bool:
async def create_table_by_sql(self, sql_statements: List[Expression | None]) -> bool:
"""
根据SQL语句创建表结构。
@@ -360,10 +312,15 @@ class GenTableCRUD(CRUDBase[GenTableModel, GenTableSchema, GenTableSchema]):
返回:
- bool: 是否创建成功。
"""
try:
# 执行SQL但不手动提交事务,由框架管理事务生命周期
for sql_statement in sql_statements:
if not sql_statement:
continue
sql = sql_statement.sql(dialect=settings.DATABASE_TYPE)
await self.db.execute(text(sql))
await self.db.flush()
return True
except Exception as e:
logger.error(f"创建表时发生错误: {e}")
@@ -433,93 +390,105 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
if not table_name:
raise ValueError("数据表名称不能为空")
# 兼容SQLite和MySQL/PostgreSQL
if settings.DATABASE_TYPE == "postgresql":
# 修复:PostgreSQL的主键/自增/注释需要关联系统表
try:
if settings.DATABASE_TYPE == "mysql":
query_sql = """
SELECT
c.column_name,
(CASE WHEN (c.is_nullable = 'NO' AND (tc.constraint_type IS DISTINCT FROM 'PRIMARY KEY')) THEN '1' ELSE '0' END) AS is_required,
(CASE WHEN (tc.constraint_type = 'PRIMARY KEY') THEN '1' ELSE '0' END) AS is_pk,
(CASE WHEN EXISTS (SELECT 1 FROM information_schema.table_constraints uc JOIN information_schema.key_column_usage kcu ON uc.constraint_name = kcu.constraint_name WHERE uc.table_name = c.table_name AND uc.table_schema = c.table_schema AND uc.constraint_type = 'UNIQUE' AND kcu.column_name = c.column_name) THEN '1' ELSE '0' END) AS is_unique,
c.column_name AS column_name,
c.column_comment AS column_comment,
c.column_type AS column_type,
c.character_maximum_length AS column_length,
c.column_default AS column_default,
c.ordinal_position AS sort,
(CASE WHEN c.column_key = 'PRI' THEN 1 ELSE 0 END) AS is_pk,
(CASE WHEN c.extra = 'auto_increment' THEN 1 ELSE 0 END) AS is_increment,
(CASE WHEN (c.is_nullable = 'NO' AND c.column_key != 'PRI') THEN 1 ELSE 0 END) AS is_nullable,
(CASE
WHEN c.column_name IN (
SELECT k.column_name
FROM information_schema.key_column_usage k
JOIN information_schema.table_constraints t
ON k.constraint_name = t.constraint_name
WHERE k.table_schema = c.table_schema
AND k.table_name = c.table_name
AND t.constraint_type = 'UNIQUE'
) THEN 1 ELSE 0
END) AS is_unique
FROM
information_schema.columns c
WHERE c.table_schema = (SELECT DATABASE())
AND c.table_name = :table_name
ORDER BY
c.ordinal_position
"""
else:
query_sql = """
SELECT
c.column_name AS column_name,
COALESCE(pgd.description, '') AS column_comment,
(CASE WHEN c.column_default LIKE 'nextval%' THEN '1' ELSE '0' END) AS is_increment,
c.udt_name AS column_type,
c.character_maximum_length AS column_length,
c.column_default AS column_default
FROM information_schema.columns c
LEFT JOIN information_schema.key_column_usage kcu
ON c.table_name = kcu.table_name AND c.column_name = kcu.column_name AND kcu.table_schema = c.table_schema
LEFT JOIN information_schema.table_constraints tc
ON tc.constraint_name = kcu.constraint_name AND tc.table_name = c.table_name AND tc.table_schema = c.table_schema
LEFT JOIN pg_catalog.pg_statio_all_tables st
ON st.relname = c.table_name
LEFT JOIN pg_catalog.pg_description pgd
ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
c.column_default AS column_default,
c.ordinal_position AS sort,
(CASE WHEN EXISTS (
SELECT 1 FROM information_schema.table_constraints tc
JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name
WHERE tc.table_name = c.table_name
AND tc.constraint_type = 'PRIMARY KEY'
AND ccu.column_name = c.column_name
) THEN 1 ELSE 0 END) AS is_pk,
(CASE WHEN c.column_default LIKE 'nextval%' THEN 1 ELSE 0 END) AS is_increment,
(CASE WHEN c.is_nullable = 'NO' THEN 1 ELSE 0 END) AS is_nullable,
(CASE WHEN EXISTS (
SELECT 1 FROM information_schema.table_constraints tc
JOIN information_schema.constraint_column_usage ccu ON tc.constraint_name = ccu.constraint_name
WHERE tc.table_name = c.table_name
AND tc.constraint_type = 'UNIQUE'
AND ccu.column_name = c.column_name
) THEN 1 ELSE 0 END) AS is_unique
FROM
information_schema.columns c
LEFT JOIN pg_catalog.pg_description pgd ON
pgd.objoid = (SELECT oid FROM pg_class WHERE relname = c.table_name)
AND pgd.objsubid = c.ordinal_position
WHERE c.table_catalog = current_database()
AND c.table_schema = 'public'
AND c.table_name = :table_name
ORDER BY c.ordinal_position
"""
elif settings.DATABASE_TYPE == "mysql":
query_sql = """
SELECT
c.column_name,
(CASE WHEN (c.is_nullable = 'NO' AND c.column_key != 'PRI') THEN '1' ELSE '0' END) AS is_required,
(CASE WHEN c.column_key = 'PRI' THEN '1' ELSE '0' END) AS is_pk,
(CASE WHEN EXISTS (SELECT 1 FROM information_schema.statistics s WHERE s.table_schema = c.table_schema AND s.table_name = c.table_name AND s.column_name = c.column_name AND s.non_unique = 0 AND s.index_name != 'PRIMARY') THEN '1' ELSE '0' END) AS is_unique,
c.ordinal_position AS sort,
c.column_comment,
(CASE WHEN c.extra = 'auto_increment' THEN '1' ELSE '0' end) AS is_increment,
c.column_type,
c.character_maximum_length AS column_length,
c.column_default AS column_default
FROM
information_schema.columns c
WHERE
c.table_schema = (SELECT DATABASE())
AND c.table_name = :table_name
ORDER BY c.ordinal_position
"""
else:
# 修复SQLite查询语句,使用PRAGMA获取表结构信息
query_sql = """
SELECT
name as column_name,
(CASE WHEN (type != '' AND pk != 1) THEN '1' ELSE '0' END) AS is_required,
(CASE WHEN pk = 1 THEN '1' ELSE '0' END) AS is_pk,
(CASE WHEN (SELECT COUNT(*) FROM pragma_index_list(:table_name) pil JOIN pragma_index_info(pil.name) pii ON 1=1 WHERE pil.unique = 1 AND pii.name = pragma_table_info.name AND pil.name NOT LIKE 'sqlite_%') > 0 THEN '1' ELSE '0' END) AS is_unique,
cid AS sort,
'' as column_comment,
(CASE WHEN type LIKE '%AUTOINCREMENT%' THEN '1' ELSE '0' END) AS is_increment,
type as column_type,
(CASE WHEN type LIKE 'varchar(%' OR type LIKE 'char(%' THEN substr(type, instr(type, '(') + 1, instr(type, ')') - instr(type, '(') - 1) ELSE NULL END) AS column_length,
dflt_value AS column_default
FROM
pragma_table_info(:table_name)
ORDER BY cid
ORDER BY
c.ordinal_position
"""
query = text(query_sql).bindparams(table_name=table_name)
rows = (await self.db.execute(query)).fetchall()
result = [
result = await self.db.execute(query)
rows = result.fetchall() if result else []
# 确保rows是可迭代对象
if not rows:
return []
columns_list = []
for row in rows:
# 防御性编程:检查row是否有足够的元素
if len(row) >= 10:
columns_list.append(
GenTableColumnOutSchema(
column_name=row[0],
is_required=row[1],
is_pk=row[2],
is_unique=row[3],
sort=row[4],
column_comment=row[5],
is_increment=row[6],
column_type=row[7],
column_length=str(row[8]) if row[8] is not None else None,
column_default=str(row[9]) if row[9] is not None else None
column_comment=row[1],
column_type=row[2],
column_length=str(row[3]) if row[3] is not None else '',
column_default=str(row[4]) if row[4] is not None else '',
sort=row[5],
is_pk=row[6],
is_increment=row[7],
is_nullable=row[8],
is_unique=row[9],
)
for row in rows
]
return result
)
return columns_list
except Exception as e:
logger.error(f"获取表{table_name}的字段列表时出错: {str(e)}")
# 确保即使出错也返回空列表而不是None
raise
async def list_gen_table_column_crud(self, search: Optional[Dict] = None, order_by: Optional[List[Dict[str, str]]] = None, preload: Optional[List[Union[str, Any]]] = None) -> Sequence[GenTableColumnModel]:
"""根据业务表字段查询业务表字段列表。
@@ -559,7 +528,7 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
data_dict = data.model_dump(exclude_unset=True)
return await self.update(id=id, data=data_dict)
async def delete_gen_table_column_by_table_id_dao(self, table_ids: List[int]) -> None:
async def delete_gen_table_column_by_table_id_crud(self, table_ids: List[int]) -> None:
"""根据业务表ID批量删除业务表字段。
参数:
@@ -577,7 +546,7 @@ class GenTableColumnCRUD(CRUDBase[GenTableColumnModel, GenTableColumnSchema, Gen
if column_ids:
await self.delete(ids=column_ids)
async def delete_gen_table_column_by_column_id_dao(self, column_ids: List[int]) -> None:
async def delete_gen_table_column_by_column_id_crud(self, column_ids: List[int]) -> None:
"""根据业务表字段ID批量删除业务表字段。
参数:
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
from typing import Optional, List
from sqlalchemy import String, Integer, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
from typing import Optional, List, Dict, Any
from sqlalchemy import String, Integer, ForeignKey, Boolean, JSON, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship, validates
from sqlalchemy.sql import expression
from app.config.setting import settings
from app.core.base_model import CreatorMixin
@@ -17,25 +18,36 @@ class GenTableModel(CreatorMixin):
__table_args__ = ({'comment': '代码生成表'})
__loader_options__ = ["columns", "creator"]
table_name: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, default='', comment='表名称')
table_comment: Mapped[Optional[str]] = mapped_column(String(500), nullable=True, default='', comment='表描述')
sub_table_name : Mapped[Optional[str]] = mapped_column(String(64), nullable=True, server_default=SqlalchemyUtil.get_server_default_null(settings.DATABASE_TYPE), comment='关联子表的表名',)
sub_table_fk_name: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, server_default=SqlalchemyUtil.get_server_default_null(settings.DATABASE_TYPE), comment='子表关联的外键名',)
class_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, default='', comment='实体类名称')
table_name: Mapped[str] = mapped_column(String(200), nullable=False, default='', comment='表名称')
table_comment: Mapped[Optional[str]] = mapped_column(String(500), nullable=True, comment='表描述')
class_name: Mapped[str] = mapped_column(String(100), nullable=False, default='', comment='实体类名称')
package_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, comment='生成包路径')
module_name: Mapped[Optional[str]] = mapped_column(String(30), nullable=True, comment='生成模块名')
business_name: Mapped[Optional[str]] = mapped_column(String(30), nullable=True, comment='生成业务名')
function_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, comment='生成功能名')
gen_type: Mapped[Optional[str]] = mapped_column(String(1), nullable=True, default='0', comment='生成代码方式(0zip压缩包 1生成项目路径)')
options: Mapped[Optional[str]] = mapped_column(String(1000), nullable=True, comment='其它生成选项')
sub_table_name: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, server_default=SqlalchemyUtil.get_server_default_null(settings.DATABASE_TYPE), comment='关联子表的表名')
sub_table_fk_name: Mapped[Optional[str]] = mapped_column(String(64), nullable=True, server_default=SqlalchemyUtil.get_server_default_null(settings.DATABASE_TYPE), comment='子表关联的外键名')
parent_menu_id: Mapped[Optional[int]] = mapped_column(Integer, nullable=True, comment='父菜单ID')
# 关系定义
columns: Mapped[List['GenTableColumnModel']] = relationship(
'GenTableColumnModel',
order_by='GenTableColumnModel.sort',
back_populates='tables',
cascade='all, delete-orphan'
)
columns: Mapped[List['GenTableColumnModel']] = relationship('GenTableColumnModel', order_by='GenTableColumnModel.sort', back_populates='table',cascade='all, delete-orphan')
@validates('table_name')
def validate_table_name(self, key: str, table_name: str) -> str:
"""验证表名不为空"""
if not table_name or not table_name.strip():
raise ValueError('表名称不能为空')
return table_name.strip()
@validates('class_name')
def validate_class_name(self, key: str, class_name: str) -> str:
"""验证类名不为空"""
if not class_name or not class_name.strip():
raise ValueError('实体类名称不能为空')
return class_name.strip()
class GenTableColumnModel(CreatorMixin):
@@ -44,38 +56,59 @@ class GenTableColumnModel(CreatorMixin):
"""
__tablename__ = 'gen_table_column'
__table_args__ = ({'comment': '代码生成表字段'})
__loader_options__ = ["tables", "creator"]
__loader_options__ = ["table", "creator"]
column_name: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, comment='列名称')
# 数据库设计表字段
column_name: Mapped[str] = mapped_column(String(200), nullable=False, comment='列名称')
column_comment: Mapped[Optional[str]] = mapped_column(String(500), nullable=True, comment='列描述')
column_type: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, comment='列类型')
column_type: Mapped[str] = mapped_column(String(100), nullable=False, comment='列类型')
column_length: Mapped[Optional[str]] = mapped_column(String(50), nullable=True, comment='列长度')
column_default: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, comment='列默认值')
python_type: Mapped[Optional[str]] = mapped_column(String(500), nullable=True, comment='PYTHON类型')
python_field: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, comment='PYTHON字段名')
is_pk: Mapped[Optional[str]] = mapped_column(String(1), nullable=True, comment='是否主键(1是)')
is_increment: Mapped[Optional[str]] = mapped_column(String(1), nullable=True, comment='是否自增(1是)')
is_required: Mapped[Optional[str]] = mapped_column(String(1), nullable=True, comment='是否必填(1是)')
is_unique: Mapped[Optional[str]] = mapped_column(String(1), nullable=True, comment='是否唯一(1是)')
is_insert: Mapped[Optional[str]] = mapped_column(String(1), nullable=True, comment='是否为插入字段(1是)')
is_edit: Mapped[Optional[str]] = mapped_column(String(1), nullable=True, comment='是否编辑字段(1是)')
is_list: Mapped[Optional[str]] = mapped_column(String(1), nullable=True, comment='是否列表字段(1是)')
is_query: Mapped[Optional[str]] = mapped_column(String(1), nullable=True, comment='是否查询字段(1是)')
query_type: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, default='EQ', comment='查询方式(等于、不等于、大于、小于、范围)')
html_type: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, comment='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)')
dict_type: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, default='', comment='字典类型')
sort: Mapped[Optional[int]] = mapped_column(Integer, nullable=True, comment='排序')
is_pk: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default=expression.false(), comment='是否主键')
is_increment: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default=expression.false(), comment='是否自增')
is_nullable: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default=expression.true(), comment='是否允许为空')
is_unique: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default=expression.false(), comment='是否唯一')
# 外键关系
table_id: Mapped[Optional[int]] = mapped_column(
# Python字段映射
python_type: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, comment='Python类型')
python_field: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, comment='Python字段名')
# 序列化配置
is_insert: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default=expression.true(), comment='是否为新增字段')
is_edit: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default=expression.true(), comment='是否编辑字段')
is_list: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default=expression.true(), comment='是否列表字段')
is_query: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, server_default=expression.false(), comment='是否查询字段')
query_type: Mapped[Optional[str]] = mapped_column(String(50), nullable=True, default=None, comment='查询方式')
# 前端展示配置
html_type: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, default='input', comment='显示类型')
dict_type: Mapped[Optional[str]] = mapped_column(String(200), nullable=True, default='', comment='字典类型')
# 排序和扩展配置
sort: Mapped[int] = mapped_column(Integer, nullable=False, default=0, comment='排序')
# 归属关系
table_id: Mapped[int] = mapped_column(
Integer,
ForeignKey('gen_table.id', ondelete='CASCADE'),
nullable=True,
nullable=False,
index=True,
comment='归属表编号'
)
# 关系定义
tables: Mapped['GenTableModel'] = relationship(
'GenTableModel',
back_populates='columns'
)
table: Mapped['GenTableModel'] = relationship('GenTableModel', back_populates='columns')
@validates('column_name')
def validate_column_name(self, key: str, column_name: str) -> str:
"""验证列名不为空"""
if not column_name or not column_name.strip():
raise ValueError('列名称不能为空')
return column_name.strip()
@validates('column_type')
def validate_column_type(self, key: str, column_type: str) -> str:
"""验证列类型不为空"""
if not column_type or not column_type.strip():
raise ValueError('列类型不能为空')
return column_type.strip()
@@ -22,7 +22,7 @@ class GenTableQueryParam:
class GenTableColumnQueryParam:
"""代码生成业务表字段查询参数
- `column_name`按like规则模糊查询(透传到CRUD层)
- `column_name`按like规则模糊查询(透传到CRUD层)
"""
def __init__(
@@ -1,23 +1,11 @@
# -*- coding:utf-8 -*-
from typing import List, Literal, Optional
from pydantic import BaseModel, ConfigDict, Field
from typing import List, Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
from app.core.base_schema import BaseSchema
class GenTableOptionSchema(BaseModel):
"""代码生成表的附加选项(存入`options`字段的JSON)。
- parent_menu_id:菜单归属;树模板依赖。
- tree_*:树形结构必需的编码/父编码/名称字段。
"""
model_config = ConfigDict(from_attributes=True)
parent_menu_id: Optional[int] = Field(default=None, description='所属父级分类')
class GenDBTableSchema(BaseModel):
"""数据库中的表信息(跨方言统一结构)。
- 供“导入表结构”与“同步结构”环节使用。
@@ -31,7 +19,10 @@ class GenDBTableSchema(BaseModel):
table_comment: Optional[str] = Field(default=None, description='表描述')
class GenTableBaseSchema(BaseModel):
class GenTableSchema(BaseModel):
"""代码生成业务表更新模型(扩展聚合字段)。
- 聚合:`columns`字段包含字段列表;`pk_column`主键字段;子表结构`sub_table`。
"""
"""代码生成业务表基础模型(创建/更新共享字段)。
- 说明:`params`为前端结构体,后端持久化为`options`的JSON。
"""
@@ -39,85 +30,69 @@ class GenTableBaseSchema(BaseModel):
table_name: str= Field(..., description='表名称')
table_comment: Optional[str] = Field(default=None, description='表描述')
sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名')
sub_table_fk_name: Optional[str] = Field(default=None, description='子表关联的外键名')
class_name: Optional[str] = Field(default=None, description='实体类名称')
package_name: Optional[str] = Field(default=None, description='生成包路径')
module_name: Optional[str] = Field(default=None, description='生成模块名')
business_name: Optional[str] = Field(default=None, description='生成业务名')
function_name: Optional[str] = Field(default=None, description='生成功能名')
gen_type: Optional[Literal['0', '1']] = Field(default=None, description='生成代码方式(0zip压缩包 1生成项目路径)')
options: Optional[str] = Field(default=None, description='其它生成选项(JSON字符串)')
description: Optional[str] = Field(default=None, description='功能描述')
sub_table_name: Optional[str] = Field(default=None, description='关联子表的表名')
sub_table_fk_name: Optional[str] = Field(default=None, description='子表关联的外键名')
parent_menu_id: Optional[int] = Field(default=None, description='所属父级分类,生成页面时候生成菜单使用')
description: Optional[str] = Field(default=None, max_length=255, description="描述")
class GenTableSchema(GenTableBaseSchema):
"""代码生成业务表更新模型(扩展聚合字段)。
- 聚合:`columns`字段包含字段列表;`pk_column`主键字段;子表结构`sub_table`。
"""
pk_column: Optional['GenTableColumnOutSchema'] = Field(default=None, description='主键信息')
sub_table: Optional['GenTableSchema'] = Field(default=None, description='子表信息')
columns: Optional[List['GenTableColumnOutSchema']] = Field(default=None, description='表列信息')
parent_menu_id: Optional[int] = Field(default=None, description='上级菜单ID字段')
parent_menu_name: Optional[str] = Field(default=None, description='上级菜单名称字段')
sub: Optional[bool] = Field(default=None, description='是否为子表')
@field_validator('table_name')
@classmethod
def table_name_update(cls, v: str) -> str:
"""更新表名称"""
if not v:
raise ValueError('表名称不能为空')
return v
class GenTableOutSchema(GenTableSchema, BaseSchema):
"""业务表输出模型(面向控制器/前端)。
- 清洗:统一处理None值,保证`columns`为列表;文本字段为空字符串。
- 兼容:既支持传入ORM对象,也支持字典输入。
"""
model_config = ConfigDict(from_attributes=True)
pk_column: Optional['GenTableColumnOutSchema'] = Field(default=None, description='主键信息')
sub_table: Optional['GenTableSchema'] = Field(default=None, description='子表信息')
sub: Optional[bool] = Field(default=None, description='是否为子表')
class GenTableColumnSchema(BaseModel):
"""代码生成业务表字段创建模型(原始字段+生成配置)。
- 原始:`column_name/column_type/column_comment` 等。
- 生成:`python_type/html_type/query_type/dict_type` 等由工具初始化。
- 标记:所有 is_* 字段默认使用字符串'1'表示启用,便于前端和模板处理。
- 从根本上解决问题:所有字段都设置了合理的默认值,避免None值问题
"""
model_config = ConfigDict(from_attributes=True)
table_id: Optional[int] = Field(default=None, description='归属表编号')
column_name: Optional[str] = Field(default=None, description='列名称')
column_comment: Optional[str] = Field(default=None, description='列描述')
column_type: Optional[str] = Field(default=None, description='列类型')
column_length: Optional[str] = Field(default=None, description='列长度')
column_default: Optional[str] = Field(default=None, description='列默认值')
python_type: Optional[str] = Field(default=None, description='python类型')
python_field: Optional[str] = Field(default=None, description='python字段名')
is_pk: Optional[str] = Field(default=None, description='是否主键(1是')
is_increment: Optional[str] = Field(default=None, description='是否自增(1是')
is_required: Optional[str] = Field(default=None, description='是否必填(1是)')
is_unique: Optional[str] = Field(default=None, description='是否唯一(1是)')
is_insert: Optional[str] = Field(default=None, description='是否为插入字段(1是')
is_edit: Optional[str] = Field(default=None, description='是否编辑字段(1是')
is_list: Optional[str] = Field(default=None, description='是否列表字段(1是')
is_query: Optional[str] = Field(default=None, description='是否查询字段(1是')
table_id: int = Field(default=0, description='归属表编号')
column_name: str = Field(default='', description='列名称')
column_comment: Optional[str] = Field(default='', description='列描述')
column_type: str = Field(default='varchar(255)', description='列类型')
column_length: Optional[str] = Field(default='', description='列长度')
column_default: Optional[str] = Field(default='', description='列默认值')
is_pk: bool = Field(default=False, description='是否主键(True是 False否)')
is_increment: bool = Field(default=False, description='是否自增(True是 False否)')
is_nullable: bool = Field(default=True, description='是否允许为空(True是 False否')
is_unique: bool = Field(default=False, description='是否唯一(True是 False否')
python_type: Optional[str] = Field(default='str', description='python类型')
python_field: Optional[str] = Field(default='', description='python字段名')
is_insert: bool = Field(default=True, description='是否为插入字段(True是 False否')
is_edit: bool = Field(default=True, description='是否编辑字段(True是 False否')
is_list: bool = Field(default=True, description='是否列表字段(True是 False否')
is_query: bool = Field(default=True, description='是否查询字段(True是 False否')
query_type: Optional[str] = Field(default=None, description='查询方式(等于、不等于、大于、小于、范围)')
html_type: Optional[str] = Field(default=None, description='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)')
dict_type: Optional[str] = Field(default=None, description='字典类型')
sort: Optional[int] = Field(default=None, description='排序')
description: Optional[str] = Field(default=None, description='功能描述')
html_type: Optional[str] = Field(default='input', description='显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件)')
dict_type: Optional[str] = Field(default='', description='字典类型')
sort: int = Field(default=0, description='排序')
class GenTableColumnOutSchema(GenTableColumnSchema, BaseSchema):
"""业务表字段输出模型(布尔派生+便捷字段)。
- 布尔:将字符串 is_* 转为布尔 `pk/increment/...`,供前端/模板快捷使用。
- 便捷:`cap_python_field` 存放大驼峰字段名(模板场景常用)。
"""
业务表字段输出模型
"""
model_config = ConfigDict(from_attributes=True)
cap_python_field: Optional[str] = Field(default=None, description='字段大写形式')
pk: Optional[bool] = Field(default=False, description='是否主键')
increment: Optional[bool] = Field(default=False, description='是否自增')
required: Optional[bool] = Field(default=False, description='是否必填')
unique: Optional[bool] = Field(default=False, description='是否唯一')
insert: Optional[bool] = Field(default=False, description='是否为插入字段')
edit: Optional[bool] = Field(default=False, description='是否编辑字段')
list: Optional[bool] = Field(default=False, description='是否列表字段')
query: Optional[bool] = Field(default=False, description='是否查询字段')
super_column: Optional[bool] = Field(default=False, description='是否为基类字段')
usable_column: Optional[bool] = Field(default=False, description='是否为基类字段白名单')
super_column: Optional[str] = Field(default='0', description='是否为基类字段(1是 0否)')
@@ -10,7 +10,6 @@ from sqlglot import parse as sqlglot_parse
from app.config.setting import settings
from app.core.logger import logger
from app.common.response import ErrorResponse
from app.core.exceptions import CustomException
from app.api.v1.module_system.auth.schema import AuthSchema
from app.utils.gen_util import GenUtils
@@ -41,10 +40,7 @@ class GenTableService:
- 备注:优先解析`options`为`GenTableOptionSchema`,设置`parent_menu_id`等选项;保证`columns`与`tables`结构完整。
"""
gen_table = await cls.get_gen_table_by_id_service(auth, table_id)
gen_tables = await cls.get_gen_table_all_service(auth)
gen_columns = await GenTableColumnService.get_gen_table_column_list_by_table_id_service(auth, table_id)
gen_table.columns = gen_columns
return dict(info=gen_table, rows=gen_columns, tables=gen_tables)
return GenTableOutSchema.model_validate(gen_table).model_dump()
@classmethod
@handle_service_exception
@@ -100,52 +96,38 @@ class GenTableService:
"""
# 检查是否有表需要导入
if not gen_table_list:
raise CustomException(msg="没有可导入的表结构")
# 检查表是否已存在
existing_tables = []
raise CustomException(msg="导入的表结构不能为空")
try:
for table in gen_table_list:
table_name = table.table_name
# 检查表是否已存在
existing_table = await GenTableCRUD(auth).get_gen_table_by_name(table_name)
if existing_table:
existing_tables.append(table_name)
# 如果有已存在的表,抛出异常
if existing_tables:
raise CustomException(msg=f"以下表已存在,不能重复导入: {', '.join(existing_tables)}")
try:
for table in gen_table_list:
table_name = table.table_name
raise CustomException(msg=f"以下表已存在,不能重复导入: {table_name}")
GenUtils.init_table(table)
add_gen_table = await GenTableCRUD(auth).add_gen_table(table)
if add_gen_table:
table.id = add_gen_table.id
# 获取数据库表的字段信息
if not table.columns:
table.columns = []
add_gen_table = await GenTableCRUD(auth).add_gen_table(GenTableSchema.model_validate(table.model_dump()))
gen_table_columns = await GenTableColumnCRUD(auth).get_gen_db_table_columns_by_name(table_name)
# 为每个字段初始化并保存到数据库
if len(gen_table_columns) > 0:
table.id = add_gen_table.id
for column in gen_table_columns:
# 将GenTableColumnOutSchema转换为GenTableColumnSchema,确保所有字段正确设置
column_schema = GenTableColumnSchema(
table_id=table.id,
column_name=column.column_name,
column_comment=column.column_comment,
column_type=column.column_type,
column_length=column.column_length if column.column_length is not None else '',
column_default=column.column_default if column.column_default is not None else '',
column_length=column.column_length,
column_default=column.column_default,
is_pk=column.is_pk,
is_increment=column.is_increment,
is_nullable=column.is_nullable,
is_unique=column.is_unique,
sort=column.sort,
python_type=column.python_type,
python_field=column.python_field,
is_pk=str(column.is_pk) if column.is_pk is not None else '0',
is_increment=str(column.is_increment) if column.is_increment is not None else '0',
is_required=str(column.is_required) if column.is_required is not None else '0',
is_unique=str(column.is_unique) if column.is_unique is not None else '0',
sort=column.sort
)
# 初始化字段属性
GenUtils.init_column_field(column_schema, table)
# 保存到数据库
await GenTableColumnCRUD(auth).create_gen_table_column_crud(column_schema)
return True
except Exception as e:
@@ -165,6 +147,8 @@ class GenTableService:
try:
# 解析SQL语句
sql_statements = sqlglot_parse(sql, dialect=settings.DATABASE_TYPE)
if not sql_statements:
raise CustomException(msg='无法解析SQL语句,请检查SQL语法')
# 校验sql语句是否为合法的建表语句
if not cls.__is_valid_create_table(sql_statements):
@@ -172,9 +156,6 @@ class GenTableService:
# 获取要创建的表名
table_names = cls.__get_table_names(sql_statements)
if not table_names:
raise CustomException(msg='无法从SQL语句中提取表名')
# 创建CRUD实例
gen_table_crud = GenTableCRUD(auth=auth)
@@ -190,17 +171,19 @@ class GenTableService:
raise CustomException(msg=f'{table_name} 已在代码生成模块中存在,请检查并修改表名后重试')
# 表不存在,执行SQL语句创建表
await gen_table_crud.create_table_by_sql(sql)
result = await gen_table_crud.create_table_by_sql(sql_statements)
if not result:
raise CustomException(msg=f'创建表 {table_names} 失败,请检查SQL语句')
# 导入表结构到代码生成模块
# 导入表结构到代码生成模块 - 简化逻辑,移除多余的None检查
gen_table_list = await cls.get_gen_db_table_list_by_name_service(auth, table_names)
import_result = await cls.import_gen_table_service(auth, gen_table_list)
return import_result
except CustomException:
# 直接传递已格式化的CustomException
raise
except Exception as e:
raise CustomException(msg=f'创建表结构失败: {str(e)}。SQL预览: {sql}')
raise CustomException(msg=f'创建表结构失败: {str(e)}')
@classmethod
def __is_valid_create_table(cls, sql_statements: List[Expression | None]) -> bool:
@@ -242,7 +225,7 @@ class GenTableService:
table = sql_statement.find(Table)
if table and table.name:
table_names.append(table.name)
return table_names
return list(set(table_names))
@classmethod
@handle_service_exception
@@ -264,7 +247,7 @@ class GenTableService:
if hasattr(gen_table_column, 'id') and gen_table_column.id:
column_schema = GenTableColumnSchema(**gen_table_column.model_dump())
await GenTableColumnCRUD(auth).update_gen_table_column_crud(gen_table_column.id, column_schema)
return result.model_dump()
return GenTableOutSchema.model_validate(result).model_dump()
except Exception as e:
raise CustomException(msg=str(e))
else:
@@ -280,7 +263,7 @@ class GenTableService:
try:
# 先删除相关的字段信息
await GenTableColumnCRUD(auth=auth).delete_gen_table_column_by_table_id_dao(ids)
await GenTableColumnCRUD(auth=auth).delete_gen_table_column_by_table_id_crud(ids)
# 再删除表信息
await GenTableCRUD(auth=auth).delete_gen_table(ids)
except Exception as e:
@@ -297,21 +280,18 @@ class GenTableService:
raise CustomException(msg='业务表不存在')
result = GenTableOutSchema.model_validate(gen_table)
return result
@classmethod
@handle_service_exception
async def get_gen_table_all_service(cls, auth: AuthSchema) -> List[GenTableOutSchema]:
"""获取所有业务表信息(列表)。"""
gen_table_all = await GenTableCRUD(auth=auth).get_gen_table_all()
gen_table_all = await GenTableCRUD(auth=auth).get_gen_table_all() or []
result = []
for gen_table in gen_table_all:
try:
# 确保转换为输出模型,并处理可能的None
# 简化转换,利用schema层的默认值处理None情况
table_out = GenTableOutSchema.model_validate(gen_table)
if table_out.columns is None:
table_out.columns = []
result.append(table_out)
except Exception as e:
logger.warning(f"转换业务表时出错: {str(e)}")
@@ -348,15 +328,11 @@ class GenTableService:
async def generate_code_service(cls, auth: AuthSchema, table_name: str) -> bool:
"""生成代码至指定路径(安全写入+可跳过覆盖)。
- 安全:限制写入在项目根目录内;越界路径自动回退到项目根目录。
- 覆盖:尊重`settings.allow_overwrite`,不允许时跳过写入。
"""
# 验证表名非空
if not table_name or not table_name.strip():
raise CustomException(msg='表名不能为空')
if not settings.allow_overwrite:
logger.error('【系统预设】不允许生成文件覆盖到本地')
raise CustomException(msg='【系统预设】不允许生成文件覆盖到本地')
env = Jinja2TemplateUtil.get_env()
render_info = await cls.__get_gen_render_info(auth, table_name)
gen_table_schema = render_info[3]
@@ -390,6 +366,9 @@ class GenTableService:
zip_buffer = io.BytesIO()
with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
for table_name in table_names:
if not table_name.strip():
continue
try:
env = Jinja2TemplateUtil.get_env()
render_info = await cls.__get_gen_render_info(auth, table_name)
@@ -419,51 +398,49 @@ class GenTableService:
if not gen_table:
raise CustomException(msg='业务表不存在')
table = GenTableOutSchema.model_validate(gen_table)
if not table.id:
raise CustomException(msg='业务表ID不能为空')
table_columns = table.columns or []
table_column_map = {column.column_name: column for column in table_columns}
db_table_columns = await GenTableColumnCRUD(auth).get_gen_db_table_columns_by_name(table_name)
# 确保db_table_columns始终是列表类型,避免None值
db_table_columns = await GenTableColumnCRUD(auth).get_gen_db_table_columns_by_name(table_name) or []
db_table_columns = [col for col in db_table_columns if col is not None]
db_table_column_names = [column.column_name for column in db_table_columns]
try:
for column in db_table_columns:
# 仅在缺省时初始化默认属性(包含 table_id 关联)
GenUtils.init_column_field(column, table)
# 确保column_length和column_default字段有默认值
if column.column_length is None:
column.column_length = ''
if column.column_default is None:
column.column_default = ''
# 利用schema层的默认值,移除多余的None检查
if column.column_name in table_column_map:
prev_column = table_column_map[column.column_name]
# 复用旧记录ID,确保执行更新
if hasattr(prev_column, 'id') and prev_column.id:
column.id = prev_column.id
# 保留用户配置的显示与查询属性
if getattr(prev_column, 'dict_type', None):
# 保留用户配置的显示与查询属性 - 使用getattr确保安全访问
if hasattr(prev_column, 'dict_type') and prev_column.dict_type:
column.dict_type = prev_column.dict_type
if getattr(prev_column, 'query_type', None):
if hasattr(prev_column, 'query_type') and prev_column.query_type:
column.query_type = prev_column.query_type
if getattr(prev_column, 'html_type', None):
if hasattr(prev_column, 'html_type') and prev_column.html_type:
column.html_type = prev_column.html_type
# 保留 is_* 标志(旧值非空则保留),主键不设置必填
def keep_str(orig, current):
return orig if (orig is not None and orig != '') else current
# 保留关键用户自定义属性 - 安全处理is_pk
is_pk_bool = False
if hasattr(prev_column, 'is_pk'):
# 处理不同类型的is_pk值
if isinstance(prev_column.is_pk, bool):
is_pk_bool = prev_column.is_pk
else:
is_pk_bool = str(prev_column.is_pk) == '1'
is_pk_bool = bool(getattr(prev_column, 'pk', False)) or (prev_column.is_pk == '1')
if not is_pk_bool:
column.is_required = keep_str(prev_column.is_required, column.is_required)
column.column_length = keep_str(prev_column.column_length, column.column_length)
column.column_default = keep_str(prev_column.column_default, column.column_default)
column.python_type = keep_str(prev_column.python_type, column.python_type)
column.python_field = keep_str(prev_column.python_field, column.python_field)
column.is_pk = keep_str(prev_column.is_pk, column.is_pk)
column.is_increment = keep_str(prev_column.is_increment, column.is_increment)
column.is_unique = keep_str(prev_column.is_unique, column.is_unique)
column.is_insert = keep_str(prev_column.is_insert, column.is_insert)
column.is_edit = keep_str(prev_column.is_edit, column.is_edit)
column.is_list = keep_str(prev_column.is_list, column.is_list)
column.is_query = keep_str(prev_column.is_query, column.is_query)
# 安全处理nullable属性
if hasattr(prev_column, 'is_nullable') and not is_pk_bool:
column.is_nullable = prev_column.is_nullable
# 保留其他重要用户设置
if hasattr(prev_column, 'python_field'):
column.python_field = prev_column.python_field or column.python_field
if hasattr(column, 'id') and column.id:
await GenTableColumnCRUD(auth).update_gen_table_column_crud(column.id, column)
@@ -477,7 +454,7 @@ class GenTableService:
if del_columns:
for column in del_columns:
if hasattr(column, 'id') and column.id:
await GenTableColumnCRUD(auth).delete_gen_table_column_by_column_id_dao([column.id])
await GenTableColumnCRUD(auth).delete_gen_table_column_by_column_id_crud([column.id])
except Exception as e:
raise CustomException(msg=f'同步失败: {str(e)}')
@@ -550,47 +527,8 @@ class GenTableColumnService:
@classmethod
@handle_service_exception
async def get_gen_table_column_list_by_table_id_service(cls, auth: AuthSchema, table_id: int) -> List[GenTableColumnOutSchema]:
async def get_gen_table_column_list_by_table_id_service(cls, auth: AuthSchema, table_id: int) -> List[Dict[str, Any]]:
"""获取业务表字段列表信息(输出模型)。"""
gen_table_column_list_result = await GenTableColumnCRUD(auth).list_gen_table_column_crud({"table_id": table_id})
result = []
for gen_table_column in gen_table_column_list_result:
try:
# 转换为输出模型前确保必要字段正确设置
# 确保is_*字段为字符串格式
if hasattr(gen_table_column, 'is_pk') and gen_table_column.is_pk is not None and not isinstance(gen_table_column.is_pk, str):
gen_table_column.is_pk = str(gen_table_column.is_pk)
if hasattr(gen_table_column, 'is_increment') and gen_table_column.is_increment is not None and not isinstance(gen_table_column.is_increment, str):
gen_table_column.is_increment = str(gen_table_column.is_increment)
if hasattr(gen_table_column, 'is_required') and gen_table_column.is_required is not None and not isinstance(gen_table_column.is_required, str):
gen_table_column.is_required = str(gen_table_column.is_required)
if hasattr(gen_table_column, 'is_unique') and gen_table_column.is_unique is not None and not isinstance(gen_table_column.is_unique, str):
gen_table_column.is_unique = str(gen_table_column.is_unique)
if hasattr(gen_table_column, 'is_insert') and gen_table_column.is_insert is not None and not isinstance(gen_table_column.is_insert, str):
gen_table_column.is_insert = str(gen_table_column.is_insert)
if hasattr(gen_table_column, 'is_edit') and gen_table_column.is_edit is not None and not isinstance(gen_table_column.is_edit, str):
gen_table_column.is_edit = str(gen_table_column.is_edit)
if hasattr(gen_table_column, 'is_list') and gen_table_column.is_list is not None and not isinstance(gen_table_column.is_list, str):
gen_table_column.is_list = str(gen_table_column.is_list)
if hasattr(gen_table_column, 'is_query') and gen_table_column.is_query is not None and not isinstance(gen_table_column.is_query, str):
gen_table_column.is_query = str(gen_table_column.is_query)
# 转换为输出模型
column_out = GenTableColumnOutSchema.model_validate(gen_table_column)
# 确保输出模型中的布尔字段正确设置
column_out.pk = column_out.is_pk == '1'
column_out.increment = column_out.is_increment == '1'
column_out.required = column_out.is_required == '1'
column_out.unique = column_out.is_unique == '1'
column_out.insert = column_out.is_insert == '1'
column_out.edit = column_out.is_edit == '1'
column_out.list = column_out.is_list == '1'
column_out.query = column_out.is_query == '1'
result.append(column_out)
except Exception as e:
logger.warning(f"转换字段模型时出错: {str(e)}")
continue
result = [GenTableColumnOutSchema.model_validate(gen_table_column).model_dump() for gen_table_column in gen_table_column_list_result]
return result
@@ -1,12 +1,10 @@
# -*- coding: utf-8 -*-
from fastapi import APIRouter, Body, Depends, Path, Query, Request, UploadFile, Form
from fastapi import APIRouter, Body, Depends, Query, Request, UploadFile, Form
from fastapi.responses import JSONResponse, StreamingResponse, FileResponse
from typing import List, Optional
# from oss2 import auth # 预留阿里云OSS,后期使用
from app.common.response import StreamResponse, SuccessResponse, ErrorResponse
from app.common.response import StreamResponse, SuccessResponse
from app.common.request import PaginationService
from app.utils.common_util import bytes2file_response
from app.core.base_params import PaginationQueryParam
+7 -7
View File
@@ -357,13 +357,13 @@ class GenConstant:
# 数据库字符串类型
COLUMNTYPE_STR = (
['character varying', 'varchar', 'character', 'char']
if settings.DATABASE_TYPE == 'postgresql'
if settings.DATABASE_TYPE == 'postgres'
else ['char', 'varchar', 'nvarchar', 'varchar2']
)
# 数据库文本类型
COLUMNTYPE_TEXT = (
['text', 'citext'] if settings.DATABASE_TYPE == 'postgresql' else ['tinytext', 'text', 'mediumtext', 'longtext']
['text', 'citext'] if settings.DATABASE_TYPE == 'postgres' else ['tinytext', 'text', 'mediumtext', 'longtext']
)
# 数据库时间类型
@@ -378,14 +378,14 @@ class GenConstant:
'timestamp without time zone',
'interval',
]
if settings.DATABASE_TYPE == 'postgresql'
if settings.DATABASE_TYPE == 'postgres'
else ['datetime', 'time', 'date', 'timestamp']
)
# 数据库字空间类型
COLUMNTYPE_GEOMETRY = (
['point', 'line', 'lseg', 'box', 'path', 'polygon', 'circle']
if settings.DATABASE_TYPE == 'postgresql'
if settings.DATABASE_TYPE == 'postgres'
else [
'geometry',
'point',
@@ -478,7 +478,7 @@ class GenConstant:
QUERY_EQ = 'EQ'
# 需要
REQUIRE = '1'
REQUIRE = True
# 数据库类型与sqlalchemy类型映射
DB_TO_SQLALCHEMY = (
@@ -537,7 +537,7 @@ class GenConstant:
'oidvector': 'ARRAY',
'pg_node_tree': 'Text',
}
if settings.DATABASE_TYPE == 'postgresql'
if settings.DATABASE_TYPE == 'postgres'
else {
# 数值类型
'TINYINT': 'SmallInteger',
@@ -647,7 +647,7 @@ class GenConstant:
'oidvector': 'list',
'pg_node_tree': 'str',
}
if settings.DATABASE_TYPE == 'postgresql'
if settings.DATABASE_TYPE == 'postgres'
else {
# 数值类型
'TINYINT': 'int',
+16 -37
View File
@@ -54,6 +54,12 @@ class Settings(BaseSettings):
REDOC_URL: str = "/redoc" # ReDoc路径
ROOT_PATH: str = "/api/v1" # API路由前缀
# ================================================= #
# ******************* alembic配置 ****************** #
# ================================================= #
# alembic 迁移文件存放路径
ALEMBIC_VERSION_DIR: Path = BASE_DIR / 'app' / 'alembic' / 'versions'
# ================================================= #
# ******************** 跨域配置 ******************** #
# ================================================= #
@@ -82,37 +88,28 @@ class Settings(BaseSettings):
SQL_DB_ENABLE: bool = True # 是否启用数据库
DATABASE_ECHO: bool | Literal['debug'] = False # 是否显示SQL日志
ECHO_POOL: bool | Literal['debug'] = False # 是否显示连接池日志
POOL_SIZE: int = 20 # 连接池大小
MAX_OVERFLOW: int = 10 # 最大溢出连接数
POOL_SIZE: int = 10 # 连接池大小
MAX_OVERFLOW: int = 20 # 最大溢出连接数
POOL_TIMEOUT: int = 30 # 连接超时时间(秒)
POOL_RECYCLE: int = 1800 # 连接回收时间(秒)
POOL_USE_LIFO: bool = True # 是否使用LIFO连接池
POOL_PRE_PING: bool = True # 是否开启连接预检
FUTURE: bool = True # 是否使用SQLAlchemy 2.0特性
AUTOCOMMIT: bool = False # 是否自动提交
AUTOFETCH: bool = False # 是否自动获取
AUTOFETCH: bool = False # 是否自动刷新
EXPIRE_ON_COMMIT: bool = False # 是否在提交时过期
# 数据库类型
DATABASE_TYPE: Literal['sqlite','mysql', 'postgresql'] = 'sqlite'
DATABASE_TYPE: Literal['mysql', 'postgres'] = 'mysql'
# MySQL/PostgreSQL/SQLite数据库连接
# MySQL/PostgreSQL数据库连接
DATABASE_HOST: str = 'localhost'
DATABASE_PORT: int = 3306
DATABASE_USER: str = 'root'
DATABASE_PASSWORD: str = 'ServBay.dev'
DATABASE_NAME: str = 'fastapiadmin'
# ================================================= #
# ******************** MongoDB配置 ******************* #
# ================================================= #
MONGO_DB_ENABLE: bool = False # 是否启用MongoDB
MONGO_DB_USER: str = ''
MONGO_DB_PASSWORD: str = ''
MONGO_DB_HOST: str = 'localhost'
MONGO_DB_PORT: int = 27017
MONGO_DB_NAME: str = 'admin'
# ================================================= #
# ******************** Redis配置 ******************* #
# ================================================= #
@@ -197,14 +194,6 @@ class Settings(BaseSettings):
# ================================================= #
SCRIPT_DIR: Path = BASE_DIR.joinpath('app/scripts/data')
# ================================================= #
# ******************* 代码生成配置 ****************** #
# ================================================= #
package_name: str = 'module_gencode' # 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool
auto_remove_pre: bool = False # 自动去除表前缀,默认是True
table_prefix: str = 'gen_' # 表前缀(生成类名不会包含表前缀,多个用逗号分隔)
allow_overwrite: bool = True # 是否允许生成文件覆盖到本地(自定义路径),默认不允许
# ================================================= #
# ******************* AI大模型配置 ****************** #
# ================================================= #
@@ -230,7 +219,6 @@ class Settings(BaseSettings):
def EVENT_LIST(self) -> List[Optional[str]]:
"""获取事件列表"""
EVENTS: List[Optional[str]] = [
"app.core.database.mongodb_connect" if self.MONGO_DB_ENABLE else None,
"app.core.database.redis_connect" if self.REDIS_ENABLE else None,
]
return EVENTS
@@ -240,29 +228,20 @@ class Settings(BaseSettings):
"""获取异步数据库连接"""
if self.DATABASE_TYPE == "mysql":
return f"mysql+asyncmy://{self.DATABASE_USER}:{quote_plus(self.DATABASE_PASSWORD)}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}?charset=utf8mb4"
elif self.DATABASE_TYPE == "postgresql":
elif self.DATABASE_TYPE == "postgres":
return f"postgresql+asyncpg://{self.DATABASE_USER}:{quote_plus(self.DATABASE_PASSWORD)}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}"
elif self.DATABASE_TYPE == "sqlite":
return f"sqlite+aiosqlite:///{self.BASE_DIR.joinpath(self.DATABASE_NAME + '.db')}?characterEncoding=UTF-8"
else:
raise ValueError(f"数据库驱动不支持: {self.DATABASE_TYPE}, 请选择 请选择 mysql、postgresql、sqlite")
raise ValueError(f"数据库驱动不支持: {self.DATABASE_TYPE}, 请选择 请选择 mysql、postgres")
@property
def DB_URI(self) -> str:
"""获取同步数据库连接"""
if self.DATABASE_TYPE == "mysql":
return f"mysql+pymysql://{self.DATABASE_USER}:{quote_plus(self.DATABASE_PASSWORD)}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}?charset=utf8mb4"
elif self.DATABASE_TYPE == "postgresql":
elif self.DATABASE_TYPE == "postgres":
return f"postgresql+psycopg2://{self.DATABASE_USER}:{quote_plus(self.DATABASE_PASSWORD)}@{self.DATABASE_HOST}:{self.DATABASE_PORT}/{self.DATABASE_NAME}"
elif self.DATABASE_TYPE == "sqlite":
return f"sqlite:///{self.BASE_DIR.joinpath(self.DATABASE_NAME + '.db')}?charset=utf8"
else:
raise ValueError(f"数据库驱动不支持: {self.DATABASE_TYPE}, 请选择 mysql、postgresql、sqlite")
@property
def MONGO_DB_URI(self) -> str:
"""获取MongoDB连接"""
return f"mongodb://{self.MONGO_DB_USER}:{self.MONGO_DB_PASSWORD}@{self.MONGO_DB_HOST}:{self.MONGO_DB_PORT}/{self.MONGO_DB_NAME}"
raise ValueError(f"数据库驱动不支持: {self.DATABASE_TYPE}, 请选择 mysql、postgres")
@property
def REDIS_URI(self) -> str:
+4 -37
View File
@@ -37,9 +37,10 @@ async_engine: AsyncEngine = create_async_engine(
pool_pre_ping=settings.POOL_PRE_PING,
future=settings.FUTURE,
pool_recycle=settings.POOL_RECYCLE,
# pool_size=settings.POOL_SIZE, # sqlite 不支持
# max_overflow=settings.MAX_OVERFLOW, # sqlite 不支持
# pool_timeout=settings.POOL_TIMEOUT, # sqlite 不支持
pool_size=settings.POOL_SIZE,
max_overflow=settings.MAX_OVERFLOW,
pool_timeout=settings.POOL_TIMEOUT,
pool_use_lifo=settings.POOL_USE_LIFO,
)
# 异步数据库会话工厂
@@ -103,37 +104,3 @@ async def redis_connect(app: FastAPI, status: bool) -> Redis | None:
else:
await app.state.redis.close()
logger.info('✅️ Redis连接已关闭')
async def mongodb_connect(app: FastAPI, status: bool) -> AsyncIOMotorClient | None:
"""
创建或关闭MongoDB连接。
参数:
- app (FastAPI): FastAPI应用实例。
- status (bool): 连接状态,True为创建连接,False为关闭连接。
返回:
- AsyncIOMotorClient | None: MongoDB异步客户端实例,如果连接失败则返回None。
"""
if not settings.MONGO_DB_ENABLE:
raise CustomException(msg="请先开启MongoDB连接", data="请启用 app/core/config.py: MONGO_DB_ENABLE")
if status:
try:
client = AsyncIOMotorClient(
settings.MONGO_DB_URI,
maxPoolSize=settings.POOL_SIZE,
minPoolSize=settings.MAX_OVERFLOW,
serverSelectionTimeoutMS=settings.POOL_TIMEOUT * 1000
)
app.state.mongo_client = client
app.state.mongo = client[settings.MONGO_DB_NAME]
data = await client.server_info()
logger.info("✅️ MongoDB连接成功...", data)
return client
except Exception as e:
raise ValueError(f"MongoDB连接失败: {e}")
else:
app.state.mongo_client.close()
logger.info("✅️ MongoDB连接已关闭")
-12
View File
@@ -6,7 +6,6 @@ from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from typing import AsyncGenerator, Optional
from fastapi import Depends, Request
from motor.motor_asyncio import AsyncIOMotorDatabase
from fastapi import Depends
from app.api.v1.module_system.user.schema import UserOutSchema
@@ -43,17 +42,6 @@ async def redis_getter(request: Request) -> Redis:
"""
return request.app.state.redis
async def mongo_getter(request: Request) -> AsyncIOMotorDatabase:
"""获取MongoDB连接
参数:
- request (Request): 请求对象
返回:
- AsyncIOMotorDatabase: MongoDB连接
"""
return request.app.state.mongo
async def get_current_user(
request: Request,
db: AsyncSession = Depends(db_getter),
-303
View File
@@ -1,303 +0,0 @@
# mongo_curd.py
import datetime
import json
from typing import Any, List, Optional, Dict, Union
from bson import ObjectId
from bson.errors import InvalidId
from bson.json_util import dumps
from fastapi.encoders import jsonable_encoder
from motor.motor_asyncio import AsyncIOMotorDatabase
from pymongo.results import InsertOneResult, UpdateResult, DeleteResult
from app.core.exceptions import CustomException
class MongoCURD:
"""
MongoDB 数据库管理器
"""
def __init__(
self,
db: AsyncIOMotorDatabase,
collection: str,
schema: Any = None
):
"""
初始化 MongoDB CURD 类。
参数:
- db (AsyncIOMotorDatabase): 数据库连接。
- collection (str): 集合名称。
- schema (Any | None): 序列化对象,传入模型以进行编码,默认 None。
返回:
- None
"""
self.db = db
self.collection = db[collection]
self.schema = schema
async def get(self, _id: Optional[str] = None, **kwargs) -> Optional[Dict]:
"""
获取单个数据,默认使用 ID 查询,否则使用关键词查询。
参数:
- _id (str | None): 数据 ID,若提供则按 ID 查询。
- kwargs (Dict[str, Any]): 查询条件键值对。
返回:
- Dict | None: 查询到的数据字典,未找到返回 None。
异常:
- CustomException: 当 ID 无效或查询发生错误时抛出。
"""
try:
if _id:
kwargs["_id"] = ObjectId(_id)
params = self.filter_condition(**kwargs)
data = await self.collection.find_one(params)
if not data:
return None
return jsonable_encoder(self.schema(**data)) if self.schema else data
except InvalidId:
raise CustomException(msg="无效的ID格式")
except Exception as e:
raise CustomException(msg=f"查询数据失败: {str(e)}")
async def create(self, data: Union[Dict, Any]) -> InsertOneResult:
"""
创建数据。
参数:
- data (Dict | Any): 要创建的数据,可为字典或可编码对象。
返回:
- InsertOneResult: 插入操作结果。
异常:
- CustomException: 创建失败时抛出。
"""
try:
if not isinstance(data, dict):
data = jsonable_encoder(data)
# 添加时间戳
now = datetime.datetime.now()
data.update({
'created_at': now,
'updated_at': now
})
result = await self.collection.insert_one(data)
if not result.acknowledged:
raise CustomException(msg="创建数据失败")
return result
except Exception as e:
raise CustomException(msg=f"创建数据失败: {str(e)}")
async def update(self, _id: str, data: Union[Dict, Any], upsert: bool = False) -> UpdateResult:
"""
更新数据。
参数:
- _id (str): 数据 ID。
- data (Dict | Any): 要更新的数据,可为字典或可编码对象。
- upsert (bool): 不存在是否插入,默认 False。
返回:
- UpdateResult: 更新操作结果。
异常:
- CustomException: ID 无效或更新失败时抛出。
"""
try:
if not isinstance(data, dict):
data = jsonable_encoder(data)
# 更新时间戳
data['updated_at'] = datetime.datetime.now()
result = await self.collection.update_one(
{'_id': ObjectId(_id)},
{'$set': data},
upsert=upsert
)
if result.matched_count == 0 and not upsert:
raise CustomException(msg="更新失败,未找到对应数据")
return result
except InvalidId:
raise CustomException(msg="无效的ID格式")
except Exception as e:
raise CustomException(msg=f"更新数据失败: {str(e)}")
async def delete(self, _id: Union[str, List[str]]) -> DeleteResult:
"""
删除数据,支持批量删除。
参数:
- _id (str | List[str]): 单个 ID 或 ID 列表。
返回:
- DeleteResult: 删除操作结果。
异常:
- CustomException: ID 无效或未删除任何数据时抛出。
"""
try:
if isinstance(_id, list):
result = await self.collection.delete_many({'_id': {'$in': [ObjectId(i) for i in _id]}})
else:
result = await self.collection.delete_one({'_id': ObjectId(_id)})
if result.deleted_count == 0:
raise CustomException(msg="删除失败,未找到对应数据")
return result
except InvalidId:
raise CustomException(msg="无效的ID格式")
except Exception as e:
raise CustomException(msg=f"删除数据失败: {str(e)}")
async def list(
self,
page_no: Optional[int] = 1,
page_size: Optional[int] = 10,
order_by: Optional[List[Dict]] = None,
**kwargs
) -> List[Dict]:
"""
查询数据列表。
参数:
- page_no (int | None): 页码,默认 1。
- page_size (int | None): 每页数量,默认 10。
- order_by (List[Dict] | None): 排序条件,形如 [{'field': '字段名', 'direction': 1}]。
- kwargs (Dict[str, Any]): 查询条件键值对。
返回:
- List[Dict]: 数据列表。
异常:
- CustomException: 查询失败时抛出。
"""
try:
params = self.filter_condition(**kwargs)
cursor = self.collection.find(params)
# 排序处理
if order_by:
sort_conditions = [(item['field'], item['direction']) for item in order_by]
cursor.sort(sort_conditions)
# 分页处理
if page_no and page_size:
cursor.skip((page_no - 1) * page_size).limit(page_size)
data_list = [json.loads(dumps(row)) async for row in cursor]
return [jsonable_encoder(self.schema(**data)) for data in data_list] if self.schema else data_list
except Exception as e:
raise CustomException(msg=f"查询列表失败: {str(e)}")
async def count(self, **kwargs) -> int:
"""
获取数据总数。
参数:
- kwargs (Dict[str, Any]): 查询条件键值对。
返回:
- int: 数据总数。
异常:
- CustomException: 统计失败时抛出。
"""
try:
params = self.filter_condition(**kwargs)
return await self.collection.count_documents(params)
except Exception as e:
raise CustomException(msg=f"统计数据失败: {str(e)}")
@staticmethod
def filter_condition(**kwargs) -> Dict:
"""
构建过滤条件。
参数:
- kwargs (Dict[str, Any]): 查询参数,支持 ('like'|'between'|'ObjectId'|'in'|'gt'|'gte'|'lt'|'lte') 等操作。
返回:
- Dict: 过滤条件字典。
异常:
- CustomException: 当 ObjectId 格式无效时抛出。
"""
params = {}
for k, v in kwargs.items():
if not v:
continue
if isinstance(v, tuple):
if v[0] == "like" and v[1]:
params[k] = {'$regex': v[1], '$options': 'i'} # i表示不区分大小写
elif v[0] == "between" and len(v[1]) == 2:
params[k] = {
'$gte': f"{v[1][0]} 00:00:00",
'$lt': f"{v[1][1]} 23:59:59"
}
elif v[0] == "ObjectId" and v[1]:
try:
params[k] = ObjectId(v[1])
except InvalidId:
raise CustomException(msg="无效的ObjectId格式")
elif v[0] == "in" and v[1]:
params[k] = {'$in': v[1]}
elif v[0] == "gt":
params[k] = {'$gt': v[1]}
elif v[0] == "gte":
params[k] = {'$gte': v[1]}
elif v[0] == "lt":
params[k] = {'$lt': v[1]}
elif v[0] == "lte":
params[k] = {'$lte': v[1]}
else:
params[k] = v
return params
# from app.api.v1.module_system.log.schema import OperationLogOutSchema
# class OperationRecordDal(MongoCURD):
# """
# 操作记录数据访问层
# """
# def __init__(self, db: AsyncIOMotorDatabase):
# """
# 初始化操作记录数据访问层。
# :param db: 数据库连接
# """
# super().__init__(
# db=db,
# collection="system_operation_log",
# schema=OperationLogOutSchema,
# )
# 创建日志到mongodb(已测试成功,可以成功创建):暂时注释,是因为该中间保存日志到mongodb(已调试成功),而我现在实现的是记录到mysql的log表
# if not settings.MONGO_DB_ENABLE:
# return response
# document = OperationLogCreateSchema(
# request_ip = request.client.host,
# request_os = user_agent.os.family,
# request_browser = user_agent.browser.family,
# request_path = request.url.path,
# request_method = request.method,
# request_payload = oper_param,
# response_code = response.status_code,
# response_json = response_data.decode(),
# description = route.name,
# creator_id = creator_id
# )
# from app.core.mongo_curd import OperationRecordDal
# from app.core.dependencies import mongo_getter
# operation_record_dal = OperationRecordDal(db = await mongo_getter(request))
# await operation_record_dal.create(data=document.model_dump())
+124 -124
View File
@@ -66,7 +66,7 @@
"permission": "system:menu:query",
"route_name": "Menu",
"route_path": "/system/menu",
"component_path": "system/menu/index",
"component_path": "module_system/menu/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -82,7 +82,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "system:menu:create",
"permission": "module_system:menu:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -101,7 +101,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "system:menu:update",
"permission": "module_system:menu:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -120,7 +120,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "system:menu:delete",
"permission": "module_system:menu:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -139,7 +139,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "system:menu:patch",
"permission": "module_system:menu:patch",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -160,10 +160,10 @@
"type": 2,
"icon": "tree",
"order": 2,
"permission": "system:dept:query",
"permission": "module_system:dept:query",
"route_name": "Dept",
"route_path": "/system/dept",
"component_path": "system/dept/index",
"component_path": "module_system/dept/index",
"status": true,
"keep_alive": true,
"always_show": false,
@@ -179,7 +179,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "system:dept:create",
"permission": "module_system:dept:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -198,7 +198,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "system:dept:update",
"permission": "module_system:dept:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -217,7 +217,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "system:dept:delete",
"permission": "module_system:dept:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -236,7 +236,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "system:dept:patch",
"permission": "module_system:dept:patch",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -257,10 +257,10 @@
"type": 2,
"icon": "el-icon-Coordinate",
"order": 3,
"permission": "system:position:query",
"permission": "module_system:position:query",
"route_name": "Position",
"route_path": "/system/position",
"component_path": "system/position/index",
"component_path": "module_system/position/index",
"status": true,
"keep_alive": true,
"always_show": false,
@@ -276,7 +276,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "system:position:create",
"permission": "module_system:position:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -295,7 +295,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "system:position:update",
"permission": "module_system:position:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -314,7 +314,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "system:position:delete",
"permission": "module_system:position:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -333,7 +333,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "system:position:patch",
"permission": "module_system:position:patch",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -352,7 +352,7 @@
"type": 3,
"icon": null,
"order": 5,
"permission": "system:position:export",
"permission": "module_system:position:export",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -371,7 +371,7 @@
"type": 3,
"icon": null,
"order": 8,
"permission": "system:role:permission",
"permission": "module_system:role:permission",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -392,10 +392,10 @@
"type": 2,
"icon": "role",
"order": 4,
"permission": "system:role:query",
"permission": "module_system:role:query",
"route_name": "Role",
"route_path": "/system/role",
"component_path": "system/role/index",
"component_path": "module_system/role/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -411,7 +411,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "system:role:create",
"permission": "module_system:role:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -430,7 +430,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "system:role:update",
"permission": "module_system:role:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -449,7 +449,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "system:role:delete",
"permission": "module_system:role:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -468,7 +468,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "system:role:patch",
"permission": "module_system:role:patch",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -487,7 +487,7 @@
"type": 3,
"icon": null,
"order": 6,
"permission": "system:role:export",
"permission": "module_system:role:export",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -508,10 +508,10 @@
"type": 2,
"icon": "el-icon-User",
"order": 5,
"permission": "system:user:query",
"permission": "module_system:user:query",
"route_name": "User",
"route_path": "/system/user",
"component_path": "system/user/index",
"component_path": "module_system/user/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -527,7 +527,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "system:user:create",
"permission": "module_system:user:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -546,7 +546,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "system:user:update",
"permission": "module_system:user:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -565,7 +565,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "system:user:delete",
"permission": "module_system:user:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -584,7 +584,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "system:user:patch",
"permission": "module_system:user:patch",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -603,7 +603,7 @@
"type": 3,
"icon": null,
"order": 5,
"permission": "system:user:export",
"permission": "module_system:user:export",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -622,7 +622,7 @@
"type": 3,
"icon": null,
"order": 6,
"permission": "system:user:import",
"permission": "module_system:user:import",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -643,10 +643,10 @@
"type": 2,
"icon": "el-icon-Aim",
"order": 6,
"permission": "system:log:query",
"permission": "module_system:log:query",
"route_name": "Log",
"route_path": "/system/log",
"component_path": "system/log/index",
"component_path": "module_system/log/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -662,7 +662,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "system:operation_log:delete",
"permission": "module_system:operation_log:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -681,7 +681,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "system:operation_log:export",
"permission": "module_system:operation_log:export",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -702,10 +702,10 @@
"type": 2,
"icon": "bell",
"order": 7,
"permission": "system:notice:query",
"permission": "module_system:notice:query",
"route_name": "Notice",
"route_path": "/system/notice",
"component_path": "system/notice/index",
"component_path": "module_system/notice/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -721,7 +721,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "system:notice:create",
"permission": "module_system:notice:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -740,7 +740,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "system:notice:update",
"permission": "module_system:notice:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -759,7 +759,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "system:notice:delete",
"permission": "module_system:notice:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -778,7 +778,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "system:notice:export",
"permission": "module_system:notice:export",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -797,7 +797,7 @@
"type": 3,
"icon": null,
"order": 5,
"permission": "system:notice:patch",
"permission": "module_system:notice:patch",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -818,10 +818,10 @@
"type": 2,
"icon": "setting",
"order": 8,
"permission": "system:param:query",
"permission": "module_system:param:query",
"route_name": "Params",
"route_path": "/system/param",
"component_path": "system/param/index",
"component_path": "module_system/param/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -837,7 +837,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "system:param:create",
"permission": "module_system:param:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -856,7 +856,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "system:param:update",
"permission": "module_system:param:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -875,7 +875,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "system:param:delete",
"permission": "module_system:param:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -894,7 +894,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "system:param:export",
"permission": "module_system:param:export",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -913,7 +913,7 @@
"type": 3,
"icon": null,
"order": 5,
"permission": "system:param:upload",
"permission": "module_system:param:upload",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -934,10 +934,10 @@
"type": 2,
"icon": "dict",
"order": 9,
"permission": "system:dict_type:query",
"permission": "module_system:dict_type:query",
"route_name": "Dict",
"route_path": "/system/dict",
"component_path": "system/dict/index",
"component_path": "module_system/dict/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -953,7 +953,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "system:dict_type:create",
"permission": "module_system:dict_type:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -972,7 +972,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "system:dict_type:update",
"permission": "module_system:dict_type:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -991,7 +991,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "system:dict_type:delete",
"permission": "module_system:dict_type:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1010,7 +1010,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "system:dict_type:export",
"permission": "module_system:dict_type:export",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1029,7 +1029,7 @@
"type": 3,
"icon": null,
"order": 5,
"permission": "system:dict_type:patch",
"permission": "module_system:dict_type:patch",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1048,7 +1048,7 @@
"type": 3,
"icon": null,
"order": 6,
"permission": "system:dict_data:query",
"permission": "module_system:dict_data:query",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1067,7 +1067,7 @@
"type": 3,
"icon": null,
"order": 7,
"permission": "system:dict_data:create",
"permission": "module_system:dict_data:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1086,7 +1086,7 @@
"type": 3,
"icon": null,
"order": 8,
"permission": "system:dict_data:update",
"permission": "module_system:dict_data:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1105,7 +1105,7 @@
"type": 3,
"icon": null,
"order": 9,
"permission": "system:dict_data:delete",
"permission": "module_system:dict_data:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1124,7 +1124,7 @@
"type": 3,
"icon": null,
"order": 10,
"permission": "system:dict_data:export",
"permission": "module_system:dict_data:export",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1143,7 +1143,7 @@
"type": 3,
"icon": null,
"order": 11,
"permission": "system:dict_data:patch",
"permission": "module_system:dict_data:patch",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1185,10 +1185,10 @@
"type": 2,
"icon": "el-icon-ShoppingCartFull",
"order": 1,
"permission": "app:myapp:query",
"permission": "module_application:myapp:query",
"route_name": "MYAPP",
"route_path": "/application/myapp",
"component_path": "application/myapp/index",
"component_path": "module_application/myapp/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -1204,7 +1204,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "app:myapp:create",
"permission": "module_application:myapp:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1223,7 +1223,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "app:myapp:update",
"permission": "module_application:myapp:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1242,7 +1242,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "app:myapp:delete",
"permission": "module_application:myapp:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1261,7 +1261,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "app:myapp:patch",
"permission": "module_application:myapp:patch",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1282,10 +1282,10 @@
"type": 2,
"icon": "el-icon-DataLine",
"order": 2,
"permission": "app:job:query",
"permission": "module_application:job:query",
"route_name": "Job",
"route_path": "/application/job",
"component_path": "application/job/index",
"component_path": "module_application/job/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -1301,7 +1301,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "app:job:create",
"permission": "module_application:job:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1320,7 +1320,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "app:job:update",
"permission": "module_application:job:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1339,7 +1339,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "app:job:delete",
"permission": "module_application:job:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1358,7 +1358,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "app:job:export",
"permission": "module_application:job:export",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1379,10 +1379,10 @@
"type": 2,
"icon": "el-icon-ToiletPaper",
"order": 3,
"permission": "app:ai:chat",
"permission": "module_application:ai:chat",
"route_name": "AI",
"route_path": "/application/ai",
"component_path": "application/ai/index",
"component_path": "module_application/ai/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -1398,7 +1398,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "app:ai:chat",
"permission": "module_application:ai:chat",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1419,10 +1419,10 @@
"type": 2,
"icon": "el-icon-ShoppingBag",
"order": 4,
"permission": "app:workflow:query",
"permission": "module_application:workflow:query",
"route_name": "Workflow",
"route_path": "/application/workflow",
"component_path": "application/workflow/index",
"component_path": "module_application/workflow/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -1459,10 +1459,10 @@
"type": 2,
"icon": "el-icon-Headset",
"order": 1,
"permission": "monitor:online:query",
"permission": "module_monitor:online:query",
"route_name": "MonitorOnline",
"route_path": "/monitor/online",
"component_path": "monitor/online/index",
"component_path": "module_monitor/online/index",
"status": true,
"keep_alive": false,
"hidden": false,
@@ -1478,7 +1478,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "monitor:online:delete",
"permission": "module_monitor:online:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1499,10 +1499,10 @@
"type": 2,
"icon": "el-icon-Odometer",
"order": 2,
"permission": "monitor:server:query",
"permission": "module_monitor:server:query",
"route_name": "MonitorServer",
"route_path": "/monitor/server",
"component_path": "monitor/server/index",
"component_path": "module_monitor/server/index",
"status": true,
"keep_alive": false,
"hidden": false,
@@ -1518,10 +1518,10 @@
"type": 2,
"icon": "el-icon-Stopwatch",
"order": 3,
"permission": "monitor:cache:query",
"permission": "module_monitor:cache:query",
"route_name": "MonitorCache",
"route_path": "/monitor/cache",
"component_path": "monitor/cache/index",
"component_path": "module_monitor/cache/index",
"status": true,
"keep_alive": false,
"hidden": false,
@@ -1537,7 +1537,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "monitor:cache:delete",
"permission": "module_monitor:cache:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1558,10 +1558,10 @@
"type": 2,
"icon": "el-icon-Files",
"order": 4,
"permission": "monitor:resource:query",
"permission": "module_monitor:resource:query",
"route_name": "Resource",
"route_path": "/monitor/resource",
"component_path": "monitor/resource/index",
"component_path": "module_monitor/resource/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -1577,7 +1577,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "monitor:resource:upload",
"permission": "module_monitor:resource:upload",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1596,7 +1596,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "monitor:resource:download",
"permission": "module_monitor:resource:download",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1615,7 +1615,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "monitor:resource:delete",
"permission": "module_monitor:resource:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1634,7 +1634,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "monitor:resource:move",
"permission": "module_monitor:resource:move",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1653,7 +1653,7 @@
"type": 3,
"icon": null,
"order": 5,
"permission": "rmonitor:resource:copy",
"permission": "module_monitor:resource:copy",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1672,7 +1672,7 @@
"type": 3,
"icon": null,
"order": 6,
"permission": "monitor:resource:rename",
"permission": "module_monitor:resource:rename",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1691,7 +1691,7 @@
"type": 3,
"icon": null,
"order": 7,
"permission": "monitor:resource:create_dir",
"permission": "module_monitor:resource:create_dir",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1710,7 +1710,7 @@
"type": 3,
"icon": null,
"order": 9,
"permission": "rmonitor:resource:export",
"permission": "module_monitor:resource:export",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1752,10 +1752,10 @@
"type": 2,
"icon": "code",
"order": 1,
"permission": "generator:gencode:query",
"permission": "module_generator:gencode:query",
"route_name": "Backcode",
"route_path": "/gencode/backcode",
"component_path": "gencode/backcode/index",
"component_path": "module_generator/backcode/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -1771,7 +1771,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "generator:gencode:query",
"permission": "module_generator:gencode:query",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1790,7 +1790,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "generator:gencode:create",
"permission": "module_generator:gencode:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1809,7 +1809,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "generator:gencode:update",
"permission": "module_generator:gencode:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1828,7 +1828,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "generator:gencode:delete",
"permission": "module_generator:gencode:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1847,7 +1847,7 @@
"type": 3,
"icon": null,
"order": 5,
"permission": "generator:gencode:import",
"permission": "module_generator:gencode:import",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1866,7 +1866,7 @@
"type": 3,
"icon": null,
"order": 6,
"permission": "generator:gencode:operate",
"permission": "module_generator:gencode:operate",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1885,7 +1885,7 @@
"type": 3,
"icon": null,
"order": 7,
"permission": "generator:gencode:code",
"permission": "module_generator:gencode:code",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1904,7 +1904,7 @@
"type": 3,
"icon": null,
"order": 8,
"permission": "generator:dblist:query",
"permission": "module_generator:dblist:query",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1923,7 +1923,7 @@
"type": 3,
"icon": null,
"order": 9,
"permission": "generator:db:sync",
"permission": "module_generator:db:sync",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1944,10 +1944,10 @@
"type": 2,
"icon": "el-icon-DataLine",
"order": 3,
"permission": "generator:demo:query",
"permission": "module_generator:demo:query",
"route_name": "Demo",
"route_path": "/gencode/demo",
"component_path": "gencode/demo/index",
"component_path": "module_generator/demo/index",
"status": true,
"keep_alive": true,
"hidden": false,
@@ -1963,7 +1963,7 @@
"type": 3,
"icon": null,
"order": 1,
"permission": "generator:demo:create",
"permission": "module_generator:demo:create",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -1982,7 +1982,7 @@
"type": 3,
"icon": null,
"order": 2,
"permission": "generator:demo:update",
"permission": "module_generator:demo:update",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -2001,7 +2001,7 @@
"type": 3,
"icon": null,
"order": 3,
"permission": "generator:demo:delete",
"permission": "module_generator:demo:delete",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -2020,7 +2020,7 @@
"type": 3,
"icon": null,
"order": 4,
"permission": "generator:demo:patch",
"permission": "module_generator:demo:patch",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -2039,7 +2039,7 @@
"type": 3,
"icon": null,
"order": 5,
"permission": "generator:demo:export",
"permission": "module_generator:demo:export",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -2058,7 +2058,7 @@
"type": 3,
"icon": null,
"order": 6,
"permission": "generator:demo:import",
"permission": "module_generator:demo:import",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -2077,7 +2077,7 @@
"type": 3,
"icon": null,
"order": 7,
"permission": "generator:demo:download",
"permission": "module_generator:demo:download",
"route_name": null,
"route_path": null,
"component_path": null,
@@ -2119,10 +2119,10 @@
"type": 4,
"icon": "api",
"order": 1,
"permission": "common:docs:query",
"permission": "module_common:docs:query",
"route_name": "Docs",
"route_path": "/common/docs",
"component_path": "common/docs/index",
"component_path": "module_common/docs/index",
"status": true,
"keep_alive": false,
"hidden": false,
@@ -2138,10 +2138,10 @@
"type": 4,
"icon": "el-icon-Document",
"order": 2,
"permission": "common:redoc:query",
"permission": "module_common:redoc:query",
"route_name": "Redoc",
"route_path": "/common/redoc",
"component_path": "common/redoc/index",
"component_path": "module_common/redoc/index",
"status": true,
"keep_alive": false,
"hidden": false,
+1 -1
View File
@@ -340,7 +340,7 @@ class SqlalchemyUtil:
:param need_explicit_null: 是否需要显式DEFAULT NULL
:return: 不同数据库方言对应的null_server_default
"""
if need_explicit_null and dialect_name == 'postgresql':
if need_explicit_null and dialect_name == 'postgres':
return null()
return None
+37 -53
View File
@@ -4,7 +4,6 @@ import re
from typing import List
from app.common.constant import GenConstant
from app.config.setting import settings
from app.utils.string_util import StringUtil
from app.api.v1.module_generator.gencode.schema import GenTableOutSchema, GenTableSchema, GenTableColumnSchema
@@ -25,8 +24,8 @@ class GenUtils:
"""
# 只有当字段为None时才设置默认值
gen_table.class_name = cls.convert_class_name(gen_table.table_name or "")
gen_table.package_name = settings.package_name
gen_table.module_name = settings.package_name.split('.')[-1]
gen_table.package_name = 'module_gencode'
gen_table.module_name = gen_table.package_name.split('.')[-1]
gen_table.business_name = gen_table.table_name.split('_')[-1]
gen_table.function_name = re.sub(r'(?:表|测试)', '', gen_table.table_comment or "")
@@ -44,32 +43,16 @@ class GenUtils:
"""
data_type = cls.get_db_type(column.column_type or "")
column_name = column.column_name or ""
if not table.id:
raise ValueError("业务表ID不能为空")
column.table_id = table.id
column.python_field = cls.to_camel_case(column_name)
# 只有当python_type为None时才设置默认类型
column.python_type = StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_PYTHON, data_type)
# 查询类型:优先根据字段语义(如以name结尾走LIKE),否则默认EQ
column.query_type = GenConstant.QUERY_LIKE
# 确保is_pk等字段为字符串格式
# 将布尔值或其他类型转换为字符串'1'或'0'
if column.is_pk is not None and not isinstance(column.is_pk, str):
column.is_pk = '1' if bool(column.is_pk) else '0'
if column.is_increment is not None and not isinstance(column.is_increment, str):
column.is_increment = '1' if bool(column.is_increment) else '0'
if column.is_required is not None and not isinstance(column.is_required, str):
column.is_required = '1' if bool(column.is_required) else '0'
if column.is_unique is not None and not isinstance(column.is_unique, str):
column.is_unique = '1' if bool(column.is_unique) else '0'
# 确保None值默认为'0'
column.is_pk = column.is_pk or '0'
column.is_increment = column.is_increment or '0'
column.is_required = column.is_required or '0'
column.is_unique = column.is_unique or '0'
# 确保column_length和column_default字段有默认值
if column.column_length is None:
column.column_length = ''
if column.column_default is None:
column.column_default = ''
@@ -103,52 +86,58 @@ class GenUtils:
column.html_type = GenConstant.HTML_INPUT
# 只有当is_insert为None时才设置插入字段(默认所有字段都需要插入)
if column.is_insert is None:
if column.is_insert:
column.is_insert = GenConstant.REQUIRE
else:
# 确保is_insert为字符串格式,并且值为'0'或'1'
column.is_insert = '1' if (column.is_insert is True or str(column.is_insert).lower() in ('1', 'true', 'yes')) else '0'
column.is_insert = False
# 只有当is_edit为None时才设置编辑字段
if column.is_edit is None:
if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_EDIT, column_name) and column.is_pk != '1':
if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_EDIT, column_name) and not column.is_pk:
column.is_edit = GenConstant.REQUIRE
else:
column.is_edit = '0'
else:
# 确保is_edit为字符串格式,并且值为'0'或'1'
column.is_edit = '1' if (column.is_edit is True or str(column.is_edit).lower() in ('1', 'true', 'yes')) else '0'
column.is_edit = False
# 只有当is_list为None时才设置列表字段
if column.is_list is None:
if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_LIST, column_name) and column.is_pk != '1':
if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_LIST, column_name) and not column.is_pk:
column.is_list = GenConstant.REQUIRE
else:
column.is_list = '0'
else:
# 确保is_list为字符串格式,并且值为'0'或'1'
column.is_list = '1' if (column.is_list is True or str(column.is_list).lower() in ('1', 'true', 'yes')) else '0'
column.is_list = False
# 只有当is_query为None时才设置查询字段
if column.is_query is None:
if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_QUERY, column_name) and column.is_pk != '1':
if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_QUERY, column_name) and not column.is_pk:
column.is_query = GenConstant.REQUIRE
# 直接设置查询类型,因为我们已经确定这是一个查询字段
if column_name.lower().endswith('name') or data_type in ['varchar', 'char', 'text']:
column.query_type = GenConstant.QUERY_LIKE
else:
column.is_query = '0'
column.query_type = GenConstant.QUERY_EQ
else:
# 确保is_query为字符串格式,并且值为'0'或'1'
column.is_query = '1' if (column.is_query is True or str(column.is_query).lower() in ('1', 'true', 'yes')) else '0'
column.is_query = False
column.query_type = None
@classmethod
def arrays_contains(cls, arr: List[str], target_value: str) -> bool:
def arrays_contains(cls, arr, target_value) -> bool:
"""
校验数组是否包含指定值
检查目标值是否在数组中
param arr: 数组
param target_value: 需要校验的值
:return: 校验结果
注意从根本上解决问题现在确保传入的参数都是正确的类型
- arr 是列表类型且在GenConstant中定义
- target_value 不会是None
参数:
- arr: 数组类型
- target_value: 目标值
返回:
- bool: 如果目标值在数组中返回True否则返回False
"""
return target_value in arr
# 从根本上解决问题,不再需要复杂的防御性检查
# 因为现在我们确保传入的arr是GenConstant中定义的列表常量
# 并且target_value在调用前已经被处理过不会是None
# 简单直接地执行包含检查
target_str = str(target_value).lower()
return any(str(item).lower() == target_str for item in arr)
@classmethod
def convert_class_name(cls, table_name: str) -> str:
@@ -161,11 +150,6 @@ class GenUtils:
返回:
- str: Python 类名
"""
auto_remove_pre = settings.auto_remove_pre
table_prefix = settings.table_prefix
if auto_remove_pre and table_prefix:
search_list = table_prefix.split(',')
table_name = cls.replace_first(table_name, search_list)
return StringUtil.convert_to_camel_case(table_name)
@classmethod
+11 -12
View File
@@ -173,15 +173,15 @@ class Jinja2TemplateUtil:
# 映射表方式简化
template_mapping = {
'controller.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/module_{module_name}/{business_name}/controller.py',
'service.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/module_{module_name}/{business_name}/service.py',
'crud.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/module_{module_name}/{business_name}/crud.py',
'model.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/module_{module_name}/{business_name}/model.py',
'param.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/module_{module_name}/{business_name}/param.py',
'schema.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/module_{module_name}/{business_name}/schema.py',
'sql.sql.j2': f'{cls.BACKEND_PROJECT_PATH}/sql/module_{module_name}/{business_name}_menu.sql',
'api.ts.j2': f'{cls.FRONTEND_PROJECT_PATH}/src/api/module_{module_name}/{business_name}.ts',
'index.vue.j2': f'{cls.FRONTEND_PROJECT_PATH}/src/views/module_{module_name}/{business_name}/index.vue'
'controller.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/controller.py',
'service.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/service.py',
'crud.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/crud.py',
'model.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/model.py',
'param.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/param.py',
'schema.py.j2': f'{cls.BACKEND_PROJECT_PATH}/app/api/v1/{module_name}/{business_name}/schema.py',
'sql.sql.j2': f'{cls.BACKEND_PROJECT_PATH}/sql/{module_name}/{business_name}_menu.sql',
'api.ts.j2': f'{cls.FRONTEND_PROJECT_PATH}/src/api/{module_name}/{business_name}.ts',
'index.vue.j2': f'{cls.FRONTEND_PROJECT_PATH}/src/views/{module_name}/{business_name}/index.vue'
}
# 查找匹配的模板路径
@@ -242,7 +242,7 @@ class Jinja2TemplateUtil:
"""
columns = gen_table.columns or []
import_list = set()
import_list.add('from sqlalchemy import Column')
for column in columns:
if column.column_type:
data_type = cls.get_db_type(column.column_type)
@@ -339,8 +339,7 @@ class Jinja2TemplateUtil:
- Set[str]: 更新后的字典类型集合
"""
for column in columns:
# 处理column.super_column, column.dict_type, column.html_type为None的情况
super_column = column.super_column if column.super_column is not None else False
super_column = column.super_column if column.super_column is not None else '0'
dict_type = column.dict_type or ''
html_type = column.html_type or ''
+1 -9
View File
@@ -30,7 +30,7 @@ DESCRIPTION = "该项目是一个基于python的web服务框架,基于fastapi
DEMO_ENABLE = False
# 数据库配置
DATABASE_TYPE = "sqlite" # sqlite、mysql、postgresql
DATABASE_TYPE = "mysql" # mysql、postgres
# 数据库配置
DATABASE_HOST = "localhost"
@@ -47,14 +47,6 @@ REDIS_USER = ''
REDIS_PASSWORD = ''
REDIS_DB_NAME = 1
# MongoDB配置
MONGO_DB_ENABLE = False
MONGO_DB_HOST = "localhost"
MONGO_DB_USER = ""
MONGO_DB_PASSWORD = ""
MONGO_DB_PORT = 27017
MONGO_DB_NAME = "admin"
# 日志配置
LOGGER_LEVEL = 'DEBUG' # 日志级别
+1 -9
View File
@@ -30,7 +30,7 @@ DESCRIPTION = "该项目是一个基于python的web服务框架,基于fastapi
DEMO_ENABLE = True
# 数据库配置
DATABASE_TYPE = "mysql" # sqlite、mysql、postgresql
DATABASE_TYPE = "mysql" # mysql、postgres
# 数据库配置
DATABASE_HOST = "172.18.52.77"
@@ -47,14 +47,6 @@ REDIS_USER = ''
REDIS_PASSWORD = 'FastApi123abc'
REDIS_DB_NAME = 1
# MongoDB配置
MONGO_DB_ENABLE = False
MONGO_DB_HOST = "172.18.52.77"
MONGO_DB_USER = "root"
MONGO_DB_PASSWORD = 'FastApi123abc'
MONGO_DB_PORT = 27017
MONGO_DB_NAME = "fastapiadmin"
# 日志配置
LOGGER_LEVEL = 'INFO'
+3 -5
View File
@@ -23,16 +23,14 @@ greenlet==3.1.1 # 协程框架
bcrypt==4.0.1 # 密码加密解析,切勿升级,如果升级,请同时升级python版本
itsdangerous==2.2.0 # 用于安全处理各种数据,如密码、密钥等
aiofiles==24.1.0 # 文件操作
redis==5.2.1 # redis 同步操作数据库(用户celery配套使用)redis 异步操作数据库 redis已经完全具备了aioredis的功能,无需重复安全,且aioredis已经不再维护也不兼容3.10+的版本
aiosqlite==0.17.0 # sqlite 异步操作数据库
redis==5.2.1 # redis 同/异步操作数据库(用户celery配套使用)redis 异步操作数据库 redis已经完全具备了aioredis的功能,无需重复安全,且aioredis已经不再维护也不兼容3.10+的版本
asyncmy==0.2.9 # mysql 异步操作数据库:基于 mysqlclientasyncmy 是 mysqlclient 的异步版本,mysqlclient 是一个 C 语言编写的 MySQL 客户端,性能较高。性能:asyncmy 通常在性能上优于 aiomysql,特别是在高并发和大数据量的场景下。
motor==3.6.0 # mongodb 驱动
PyMySQL==1.1.2 # mysql 同步步操作数据库基于 pymysqlaiomysql 是 pymysql 的异步版本,pymysql 是一个纯 Python 实现的 MySQL 客户端。成熟度:aiomysql 相对较为成熟,社区支持较好,文档也比较完善。
asyncpg==0.30.0 # postgresql 异步操作数据库基于 psycopg2asyncpg 是 psycopg2 的异步版本,psycopg2 是一个 pure-Python PostgreSQL 数据库适配器。性能:asyncpg 通常在性能上优于 psycopg2,特别是在高并发和大数据量的场景下。
psycopg2==2.9.10 # postgresql 同步操作数据库基于 psycopg2psycopg2 是一个 pure-Python PostgreSQL 适配器。
PyMySQL==1.1.2 # mysql 异步操作数据库基于 pymysqlaiomysql 是 pymysql 的异步版本,pymysql 是一个纯 Python 实现的 MySQL 客户端。成熟度:aiomysql 相对较为成熟,社区支持较好,文档也比较完善。
cryptography==45.0.2 # mysql8 密码加密
openai==1.55.2 # ai 大模型
oss2==2.18.4 # 阿里云对象存储
rich==13.9.4 # 终端打印美化
sqlglot[rs]==27.8.0 # sql 解析
pydantic_validation_decorator==0.1.4 # 模型验证
loguru
@@ -278,7 +278,7 @@ CREATE TABLE `gen_table_column` (
`python_field` varchar(200) DEFAULT NULL COMMENT 'PYTHON字段名',
`is_pk` varchar(1) DEFAULT NULL COMMENT '是否主键(1是)',
`is_increment` varchar(1) DEFAULT NULL COMMENT '是否自增(1是)',
`is_required` varchar(1) DEFAULT NULL COMMENT '是否必填(1是)',
`is_nullable` varchar(1) DEFAULT NULL COMMENT '是否必填(1是)',
`is_unique` varchar(1) DEFAULT NULL COMMENT '是否唯一(1是)',
`is_insert` varchar(1) DEFAULT NULL COMMENT '是否为插入字段(1是)',
`is_edit` varchar(1) DEFAULT NULL COMMENT '是否编辑字段(1是)',
@@ -863,7 +863,7 @@ CREATE TABLE public.gen_table_column (
python_field character varying(200),
is_pk character varying(1),
is_increment character varying(1),
is_required character varying(1),
is_nullable character varying(1),
is_unique character varying(1),
is_insert character varying(1),
is_edit character varying(1),
@@ -955,10 +955,10 @@ COMMENT ON COLUMN public.gen_table_column.is_increment IS '是否自增(1是
--
-- Name: COLUMN gen_table_column.is_required; Type: COMMENT; Schema: public; Owner: tao
-- Name: COLUMN gen_table_column.is_nullable; Type: COMMENT; Schema: public; Owner: tao
--
COMMENT ON COLUMN public.gen_table_column.is_required IS '是否必填(1是)';
COMMENT ON COLUMN public.gen_table_column.is_nullable IS '是否必填(1是)';
--
@@ -2788,7 +2788,7 @@ COPY public.gen_table (table_name, table_comment, sub_table_name, sub_table_fk_n
-- Data for Name: gen_table_column; Type: TABLE DATA; Schema: public; Owner: tao
--
COPY public.gen_table_column (column_name, column_comment, column_type, column_length, column_default, python_type, python_field, is_pk, is_increment, is_required, is_unique, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, table_id, creator_id, id, description, created_at, updated_at) FROM stdin;
COPY public.gen_table_column (column_name, column_comment, column_type, column_length, column_default, python_type, python_field, is_pk, is_increment, is_nullable, is_unique, is_insert, is_edit, is_list, is_query, query_type, html_type, dict_type, sort, table_id, creator_id, id, description, created_at, updated_at) FROM stdin;
\.
+7 -3
View File
@@ -1,8 +1,12 @@
# -*- coding: utf-8 -*-
from typing import Optional
from sqlalchemy import String, Integer, Text, DateTime, Boolean, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
{% for model_import in model_import_list %}
{{ model_import }}
{% endfor %}
{% if table.sub %}
from sqlalchemy.orm import relationship
{% endif %}
from sqlalchemy.orm import Mapped, mapped_column
from app.core.base_model import CreatorMixin
+7 -5
View File
@@ -15,11 +15,13 @@ class {{ class_name }}QueryParam:
{{ column.column_name }}: Optional[{{ column.python_type }}] = Query(None, description="{{ column.column_comment }}"),
{% endif %}
{% endfor %}
{% for column in columns %}
{% if column.column_name == 'status' %}
status: Optional[bool] = Query(None, description="是否启用"),
{% if column.column_name == 'EQ' %}
{{ column.column_name }}: Optional[{{ column.python_type }}] = Query(None, description="{{ column.column_comment }}"),
{% endif %}
{% endfor %}
creator: Optional[int] = Query(None, description="创建人"),
start_time: Optional[DateTimeStr] = Query(None, description="开始时间", example="2025-01-01 00:00:00"),
end_time: Optional[DateTimeStr] = Query(None, description="结束时间", example="2025-12-31 23:59:59"),
@@ -33,12 +35,12 @@ class {{ class_name }}QueryParam:
{% endfor %}
# 精确查询字段
self.creator_id = creator
{% for column in columns %}
{% if column.column_name == 'status' %}
self.status = status
{% if column.query_type == 'EQ' %}
self.{{ column.column_name }} = {{ column.column_name }}
{% endif %}
{% endfor %}
self.creator_id = creator
# 时间范围查询
if start_time and end_time:
+1 -27
View File
@@ -1,37 +1,11 @@
# -*- coding:utf-8 -*-
{% set pkField = pk_column.python_field %}
{% set pk_field = pk_column.python_field | camel_to_snake %}
{% set pkParentheseIndex = pk_column.column_comment.find("") %}
{% set pk_field_comment = pk_column.column_comment[:pkParentheseIndex] if pkParentheseIndex != -1 else pk_column.column_comment %}
{% set vo_field_required = namespace(has_required=False) %}
{% set vo_field_daterange = namespace(has_daterange=False) %}
{% for column in columns %}
{% if column.required %}
{% set vo_field_required.has_required = True %}
{% endif %}
{% if column.html_type == "datetime" and column.query_type == "BETWEEN" %}
{% set vo_field_daterange.has_daterange = True %}
{% endif %}
{% endfor %}
{% set sub_vo_field_required = namespace(has_required=False) %}
{% if table.sub %}
{% for sub_column in subTable.columns %}
{% if sub_column.required %}
{% set sub_vo_field_required.has_required = True %}
{% endif %}
{% endfor %}
{% endif %}
{% for vo_import in vo_import_list %}
{{ vo_import }}
{% endfor %}
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
{% if table.sub %}
from typing import List, Optional
{% else %}
from typing import Optional
{% endif %}
from pydantic import BaseModel, ConfigDict, Field
from app.core.base_schema import BaseSchema
+1 -1
View File
@@ -55,7 +55,7 @@ VALUES ('{{ function_name }}批量状态修改', 3, 7, {{ b_true }}, '{{ permiss
INSERT INTO `system_menu` (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
VALUES ('{{ function_name }}下载导入模板', 3, 8, {{ b_true }}, '{{ permission_prefix }}:download', NULL, NULL, NULL, NULL, NULL, {{ b_false }}, {{ b_true }}, {{ b_false }}, '{{ function_name }}下载导入模板', NULL, {{ b_false }}, @parentId, '', now(), now());
{% elif db_type == 'postgresql' %}
{% elif db_type == 'postgres' %}
-- 父菜单 + 子按钮(PostgreSQL 使用 CTE 获取父ID
WITH parent AS (
INSERT INTO public.system_menu (name, type, {{ order_col }}, status, permission, icon, route_name, route_path, component_path, redirect, hidden, keep_alive, always_show, title, params, affix, parent_id, description, created_at, updated_at)
+81 -50
View File
@@ -42,7 +42,7 @@
</el-form-item>
<el-form-item>
<el-button v-hasPerm="['{{ module_name }}:{{ business_name }}:query']" type="primary" icon="search" @click="handleQuery">查询</el-button>
<el-button v-hasPerm="['{{ module_name }}:{{ business_name|lower }}:query']" icon="refresh" @click="handleResetQuery">重置</el-button>
<el-button v-hasPerm="['{{ module_name }}:{{ business_name }}:query']" icon="refresh" @click="handleResetQuery">重置</el-button>
<template v-if="isExpandable">
<el-link class="ml-3" type="primary" underline="never" @click="isExpand = !isExpand">
{{ '{{' }} isExpand ? "收起" : "展开" {{ '}}' }}
@@ -214,7 +214,7 @@
{% set column_comment = column.column_comment if column.column_comment else '' %}
{% set parentheseIndex = column_comment.find("") %}
{% set comment = column_comment[:parentheseIndex] if parentheseIndex != -1 else column_comment %}
{% set required = 'true' if column.is_required == '1' else 'false' %}
{% set required = 'true' if column.is_nullable == '1' else 'false' %}
{% if column.python_field == "status" %}
<el-form-item label="状态" prop="status" :required="true">
@@ -268,8 +268,8 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="handleCloseDialog">取消</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" v-hasPerm="['{{ module_name }}:{{ business_name|lower }}:submit']" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else v-hasPerm="['{{ module_name }}:{{ business_name|lower }}:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
<el-button v-if="dialogVisible.type !== 'detail'" v-hasPerm="['{{ module_name }}:{{ business_name }}:submit']" type="primary" @click="handleSubmit">确定</el-button>
<el-button v-else v-hasPerm="['{{ module_name }}:{{ business_name }}:detail']" type="primary" @click="handleCloseDialog">确定</el-button>
</div>
</template>
</el-dialog>
@@ -292,6 +292,10 @@
</template>
<script setup lang="ts">
defineOptions({
name: "{{ class_name }}",
inheritAttrs: false,
});
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ResultEnum } from '@/enums/api/result.enum'
@@ -358,11 +362,11 @@ const curdContentConfig = {
exportsAction: async (params: any) => {
const query: any = { ...params };
if (typeof query.status === 'string') {
query.status = query.status === 'true'
query.status = query.status === 'true';
}
query.page_no = 1
query.page_size = 9999
const all: any[] = []
query.page_no = 1;
query.page_size = 9999;
const all: any[] = [];
while (true) {
const res = await {{ class_name }}API.list(query)
const items = res.data?.data?.items || []
@@ -371,7 +375,7 @@ const curdContentConfig = {
if (all.length >= total || items.length === 0) break
query.page_no += 1
}
return all
return all;
},
} as unknown as IContentConfig
@@ -392,11 +396,31 @@ const formData = reactive({
{% endfor %}
})
// 表单验证规则(必填项按 is_required 生成)
// 定义初始表单数据常量
const initialFormData = {
id: undefined,
{% for column in columns %}
{% if column.is_insert == "1" or column.is_edit == "1" %}
{{ column.python_field }}: {{ 'true' if column.python_field == 'status' else ('' if column.html_type == 'textarea' else 'undefined') }},
{% endif %}
{% endfor %}
}
// 重置表单
async function resetForm() {
if (dataFormRef.value) {
dataFormRef.value.resetFields();
dataFormRef.value.clearValidate();
}
// 完全重置 formData 为初始状态
Object.assign(formData, initialFormData);
}
// 表单验证规则(必填项按 is_nullable 生成)
const rules = reactive({
{% for column in columns %}
{% if column.is_insert == "1" or column.is_edit == "1" %}
{% set required = 'true' if column.is_required == '1' else 'false' %}
{% set required = 'true' if column.is_nullable == '1' else 'false' %}
{{ column.python_field }}: [
{ required: {{ required }}, message: '请输入{{ column.column_comment or column.python_field }}', trigger: 'blur' },
],
@@ -405,18 +429,18 @@ const rules = reactive({
})
// 详情表单
const detailFormData = ref<any>({})
const detailFormData = ref<any>({});
// 统一日期范围
const dateRange = ref<[Date, Date] | []>([])
const dateRange = ref<[Date, Date] | []>([]);
function handleDateRangeChange(range: [Date, Date]) {
dateRange.value = range
dateRange.value = range;
if (range && range.length === 2) {
queryFormData.start_time = formatToDateTime(range[0])
queryFormData.end_time = formatToDateTime(range[1])
queryFormData.start_time = formatToDateTime(range[0]);
queryFormData.end_time = formatToDateTime(range[1]);
} else {
queryFormData.start_time = undefined
queryFormData.end_time = undefined
queryFormData.start_time = undefined;
queryFormData.end_time = undefined;
}
}
@@ -436,22 +460,22 @@ const queryFormData = reactive({
// 加载表格数据
async function loadingData() {
loading.value = true
loading.value = true;
try {
const response = await {{ class_name }}API.list(queryFormData)
pageTableData.value = response.data.data.items
total.value = response.data.data.total
const response = await {{ class_name }}API.list(queryFormData);
pageTableData.value = response.data.data.items;
total.value = response.data.data.total;
} catch (error) {
console.error(error)
console.error(error);
} finally {
loading.value = false
loading.value = false;
}
}
// 查询(重置页码后获取数据)
async function handleQuery() {
queryFormData.page_no = 1
loadingData()
queryFormData.page_no = 1;
loadingData();
}
// 选择创建人后触发查询
@@ -461,12 +485,12 @@ function handleConfirm() {
// 重置查询
async function handleResetQuery() {
queryFormRef.value.resetFields()
queryFormData.page_no = 1
dateRange.value = []
queryFormData.start_time = undefined
queryFormData.end_time = undefined
loadingData()
queryFormRef.value.resetFields();
queryFormData.page_no = 1;
dateRange.value = [];
queryFormData.start_time = undefined;
queryFormData.end_time = undefined;
loadingData();
}
// 行复选框选中项变化
@@ -476,27 +500,28 @@ function handleSelectionChange(selection: any[]) {
}
// 关闭弹窗
function handleCloseDialog() {
dialogVisible.visible = false
async function handleCloseDialog() {
dialogVisible.visible = false;
resetForm();
}
// 打开弹窗
async function handleOpenDialog(type: 'create' | 'update' | 'detail', id?: number) {
dialogVisible.type = type
if (id) {
const response = await {{ class_name }}API.detail(id)
const response = await {{ class_name }}API.detail(id);
if (type === 'detail') {
dialogVisible.title = '详情'
Object.assign(detailFormData.value, response.data.data)
dialogVisible.title = '详情';
Object.assign(detailFormData.value, response.data.data);
} else if (type === 'update') {
dialogVisible.title = '修改'
Object.assign(formData, response.data.data)
dialogVisible.title = '修改';
Object.assign(formData, response.data.data);
}
} else {
dialogVisible.title = '新增{{ function_name }}'
formData.id = undefined
dialogVisible.title = '新增{{ function_name }}';
formData.id = undefined;
}
dialogVisible.visible = true
dialogVisible.visible = true;
}
// 提交表单
@@ -507,12 +532,18 @@ async function handleSubmit() {
try {
const id = formData.id
if (id) {
await {{ class_name }}API.update(id, { id, ...formData })
await {{ class_name }}API.update(id, { id, ...formData });
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
handleResetQuery();
} else {
await {{ class_name }}API.create(formData)
await {{ class_name }}API.create(formData);
dialogVisible.visible = false;
resetForm();
handleCloseDialog();
handleResetQuery();
}
dialogVisible.visible = false
handleResetQuery()
} catch (error) {
console.error(error)
} finally {
@@ -531,8 +562,8 @@ async function handleDelete(ids: number[]) {
})
.then(async () => {
try {
loading.value = true
await {{ class_name }}API.delete(ids)
loading.value = true;
await {{ class_name }}API.delete(ids);
handleResetQuery()
} catch (error) {
console.error(error)
@@ -555,7 +586,7 @@ async function handleMoreClick(status: boolean) {
}).then(async () => {
try {
loading.value = true
await {{ class_name }}API.batchAvailable({ ids: selectIds.value, status })
await {{ class_name }}API.batchAvailable({ ids: selectIds.value, status });
handleResetQuery()
} catch (error) {
console.error(error)
@@ -585,7 +616,7 @@ function handleOpenExportsModal() {
// 处理上传
const handleUpload = async (formData: FormData) => {
try {
const response = await {{ class_name }}API.import(formData)
const response = await {{ class_name }}API.import(formData);
if (response.data.code === ResultEnum.SUCCESS) {
ElMessage.success(`${response.data.msg}${response.data.data}`)
importDialogVisible.value = false
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "fastapi-vue3-admin",
"name": "FastapiAdmin",
"description": "Vue3 + Vite + TypeScript + Element-Plus 的后台管理模板",
"version": "2.0.0",
"private": true,
@@ -4,8 +4,8 @@ const API_PATH = "/generator/gencode";
const GencodeAPI = {
// 查询生成表数据
listTable(query: GenTableQueryParam) {
return request<ApiResponse<PageResult<GenTableOutVO[]>>>({
listTable(query: GenTablePageQuery) {
return request<ApiResponse<PageResult<GenTableSchema[]>>>({
url: `${API_PATH}/list`,
method: 'get',
params: query
@@ -13,8 +13,8 @@ const GencodeAPI = {
},
// 查询db数据库列表
listDbTable(query: GenTableQueryParam) {
return request<ApiResponse<PageResult<DatabaseTable[]>>>({
listDbTable(query: DBTablePageQuery) {
return request<ApiResponse<PageResult<DBTableSchema[]>>>({
url: `${API_PATH}/db/list`,
method: 'get',
params: query
@@ -32,7 +32,7 @@ const GencodeAPI = {
// 查询表详细信息
getGenTableDetail(table_id: number) {
return request<ApiResponse<GenTableDetailResult>>({
return request<ApiResponse<GenTableSchema>>({
url: `${API_PATH}/detail/${table_id}`,
method: 'get'
})
@@ -112,41 +112,32 @@ export interface GeneratorPreviewVO {
content: string;
}
/** 数据表分页查询参数 */
export interface GenTableQueryParam {
page_no: number;
page_size: number;
table_name?: string;
table_comment?: string;
start_time?: string;
end_time?: string;
}
export interface TablePageQuery extends PageQuery {
/** 代码生成表分页查询参数 */
export interface DBTablePageQuery extends PageQuery {
/** 表名称 */
table_name?: string;
/** 表描述 */
table_comment?: string;
}
/** 数据表分页对象 */
export interface TablePageVO {
/** 表名称 */
table_name: string;
/** 表描述 */
table_comment: string;
/** 数据库名称 */
database_name: string;
/** 表单类型 */
table_type: string;
/**数据库表基本信息接口 */
export interface DBTableSchema {
database_name?: string;
table_name?: string;
table_comment?: string;
table_type?: string;
}
/** 代码生成表输出对象 */
export interface GenTableOutVO {
/** 代码生成表分页查询参数 */
export interface GenTablePageQuery extends PageQuery {
/** 表名称 */
table_name?: string;
/** 表描述 */
table_comment?: string;
}
/** 代码生成业务表模型 */
export interface GenTableSchema {
/** 主键 */
id?: number;
/** 表名称 */
@@ -167,32 +158,16 @@ export interface GenTableOutVO {
business_name?: string;
/** 生成功能名 */
function_name?: string;
/** 生成代码方式(0zip压缩包 1自定义路径) */
gen_type?: string;
/** 其它生成选项 */
options?: GenTableOptionModel;
}
/** 表选项模型 */
export interface GenTableOptionModel {
/** 所属父级分类 */
parent_menu_id?: number;
}
/** 代码生成业务表模型 */
export interface GenTableSchema extends GenTableOutVO {
/** 表描述 */
description?: string;
/** 上级菜单ID字段 */
parent_menu_id?: number;
/** 上级菜单名称字段 */
parent_menu_name?: string;
/** 表列信息 */
columns: GenTableColumnSchema[];
/** 主键信息 */
pk_column?: GenTableColumnOutSchema;
pk_column?: GenTableColumnSchema;
/** 子表信息 */
sub_table?: GenTableSchema;
/** 表列信息 */
columns: GenTableColumnOutSchema[];
/** 是否为子表 */
sub?: boolean;
}
@@ -218,21 +193,21 @@ export interface GenTableColumnSchema {
/** PYTHON字段名 */
python_field?: string;
/** 是否主键(1是) */
is_pk?: string;
is_pk?: boolean;
/** 是否自增(1是) */
is_increment?: string;
is_increment?: boolean;
/** 是否必填(1是) */
is_required?: string;
is_nullable?: boolean;
/** 是否唯一(1是) */
is_unique?: string;
/** 是否为插入字段(1是) */
is_insert?: string;
is_unique?: boolean;
/** 是否为新增字段(1是) */
is_insert?: boolean;
/** 是否编辑字段(1是) */
is_edit?: string;
is_edit?: boolean;
/** 是否列表字段(1是) */
is_list?: string;
is_list?: boolean;
/** 是否查询字段(1是) */
is_query?: string;
is_query?: boolean;
/** 查询方式(等于、不等于、大于、小于、范围) */
query_type?: string;
/** 显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件) */
@@ -245,85 +220,3 @@ export interface GenTableColumnSchema {
description?: string;
}
/** 代码生成业务表列输出模型 */
export interface GenTableColumnOutSchema extends GenTableColumnSchema {
/** 字段大写形式 */
cap_python_field?: string;
/** 是否主键 */
pk?: boolean;
/** 是否自增 */
increment?: boolean;
/** 是否必填 */
required?: boolean;
/** 是否唯一 */
unique?: boolean;
/** 是否为插入字段 */
insert?: boolean;
/** 是否编辑字段 */
edit?: boolean;
/** 是否列表字段 */
list?: boolean;
/** 是否查询字段 */
query?: boolean;
/** 是否为基类字段 */
super_column?: boolean;
/** 是否为基类字段白名单 */
usable_column?: boolean;
}
/** 表详情查询结果 */
export interface GenTableDetailResult {
/** 表信息 */
info: GenTableOutVO;
/** 表列信息 */
rows: GenTableColumnOutSchema[];
/** 所有表信息 */
tables: GenTableOutVO[];
}
/**
*
*/
export interface DatabaseTable {
database_name?: string;
table_name?: string;
table_comment?: string;
table_type?: string;
}
/**
*
*/
export interface TableColumn {
column_name: string;
column_comment: string;
}
/**
*
*/
export interface TableInfo {
table_name?: string;
table_comment?: string;
columns?: TableColumn[];
}
/**
*
*/
export interface ImportTableQueryForm {
page_no: number;
page_size: number;
table_name?: string;
table_comment?: string;
}
/**
*
*/
export interface BasicInfoFormData {
table_name?: string;
table_comment?: string;
class_name?: string;
description?: string;
}
@@ -1,5 +1,5 @@
import request from "@/utils/request";
import { MenuTable, MenuForm } from "@/api/system/menu";
import { MenuTable, MenuForm } from "@/api/module_system/menu";
const API_PATH = "/system/user";
@@ -75,7 +75,7 @@
</template>
<script setup lang="ts">
import NoticeAPI, { NoticeTable } from "@/api/system/notice";
import NoticeAPI, { NoticeTable } from "@/api/module_system/notice";
import router from "@/router";
import { useNoticeStore } from "@/store";
@@ -49,7 +49,7 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { UploadRawFile, UploadRequestOptions, ElMessage, type UploadUserFile } from "element-plus";
import ParamsAPI from '@/api/system/params';
import ParamsAPI from '@/api/module_system/params';
const props = defineProps({
/**
@@ -111,7 +111,7 @@ import Notification from "@/components/Notification/index.vue";
import LockDialog from './LockDialog.vue'
import LockPage from './LockPage.vue'
import Guide from '@/components/Guide/index.vue'
import ConfigInfoDrawer from "@/views/system/param/components/ConfigInfoDrawer.vue"
import ConfigInfoDrawer from "@/views/module_system/param/components/ConfigInfoDrawer.vue"
const { t } = useI18n();
+2 -2
View File
@@ -20,7 +20,7 @@ export const constantRoutes: RouteRecordRaw[] = [
path: "/login",
name: "Login",
meta: { hidden: true },
component: () => import("@/views/system/auth/index.vue"),
component: () => import("@/views/module_system/auth/index.vue"),
},
{
path: "/401",
@@ -76,7 +76,7 @@ export const constantRoutes: RouteRecordRaw[] = [
path: "internal-app/:appId",
name: "InternalApp",
meta: { title: "内部应用", icon: "Monitor", hidden: true, keepAlive: false },
component: () => import("@/views/application/myapp/components/InternalApp.vue"),
component: () => import("@/views/module_application/myapp/components/InternalApp.vue"),
},
],
},
+1 -1
View File
@@ -1,5 +1,5 @@
import { store } from "@/store";
import ParamsAPI, { ConfigTable } from "@/api/system/params";
import ParamsAPI, { ConfigTable } from "@/api/module_system/params";
interface ConfigState {
// 网站信息
+1 -1
View File
@@ -1,5 +1,5 @@
import { store } from "@/store";
import DictAPI, { DictDataTable } from "@/api/system/dict";
import DictAPI, { DictDataTable } from "@/api/module_system/dict";
export const useDictStore = defineStore("dict", {
state: () => ({
+1 -1
View File
@@ -1,5 +1,5 @@
import { store } from "@/store";
import NoticeAPI, { NoticeTable } from "@/api/system/notice";
import NoticeAPI, { NoticeTable } from "@/api/module_system/notice";
export const useNoticeStore = defineStore("notice", {
state: () => ({
@@ -1,7 +1,7 @@
import type { RouteRecordRaw } from "vue-router";
import router, { constantRoutes } from "@/router";
import { store, useUserStore } from "@/store";
import { MenuTable } from "@/api/system/menu";
import { MenuTable } from "@/api/module_system/menu";
const modules = import.meta.glob("../../views/**/**.vue");
const Layout = () => import("@/layouts/index.vue");
+3 -3
View File
@@ -1,8 +1,8 @@
import { store, useTagsViewStore, usePermissionStoreHook, useDictStoreHook } from "@/store";
import AuthAPI, {type LoginFormData } from "@/api/system/auth";
import UserAPI, {type UserInfo } from "@/api/system/user";
import type { MenuTable } from "@/api/system/menu";
import AuthAPI, {type LoginFormData } from "@/api/module_system/auth";
import UserAPI, {type UserInfo } from "@/api/module_system/user";
import type { MenuTable } from "@/api/module_system/menu";
import { Auth } from "@/utils/auth";
export const useUserStore = defineStore("user", {
+1 -1
View File
@@ -215,7 +215,7 @@
<script lang="ts" setup>
import type { FormInstance, UploadRequestOptions, UploadFile, ElUpload, ComponentSize } from 'element-plus'
import UserAPI, { type InfoFormState, type PasswordFormState } from '@/api/system/user';
import UserAPI, { type InfoFormState, type PasswordFormState } from '@/api/module_system/user';
import { useUserStore, useDictStore } from "@/store";
import { useUserStoreHook } from "@/store/modules/user.store";
import { Camera } from '@element-plus/icons-vue';
+1 -1
View File
@@ -186,7 +186,7 @@ defineOptions({
import { EChartsOption } from 'echarts'
import { useUserStore } from "@/store/index";
import { greetings } from '@/utils/common';
import NoticeAPI, { NoticeTable } from '@/api/system/notice';
import NoticeAPI, { NoticeTable } from '@/api/module_system/notice';
import { ref, onMounted, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
@@ -172,7 +172,7 @@ const props = defineProps({
}
})
import JobAPI, { JobLogPageQuery, JobLogTable } from "@/api/application/job";
import JobAPI, { JobLogPageQuery, JobLogTable } from "@/api/module_application/job";
import { useAppStore } from "@/store/modules/app.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
import ExportModal from "@/components/CURD/ExportModal.vue";
@@ -594,9 +594,6 @@
:selection-data="selectionRows"
/>
</div>
</template>
<script lang="ts" setup>
@@ -605,12 +602,12 @@ defineOptions({
inheritAttrs: false,
});
import JobAPI, { JobTable, JobForm, JobPageQuery } from "@/api/application/job";
import JobAPI, { JobTable, JobForm, JobPageQuery } from "@/api/module_application/job";
import IntervalTab from "@/components/IntervalTab/index.vue";
import { useDictStore } from "@/store/index";
import { vue3CronPlus } from "vue3-cron-plus";
import "vue3-cron-plus/dist/index.css"; //
import JobLogDrawer from "@/views/application/job/components/JobLogDrawer.vue"
import JobLogDrawer from "@/views/module_application/job/components/JobLogDrawer.vue"
import OperationColumn from "@/components/OperationColumn/index.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import type { IContentConfig } from "@/components/CURD/types";
@@ -232,7 +232,7 @@ import { useTagsViewStore } from "@/store";
import { useRouter } from "vue-router";
import { DeviceEnum } from "@/enums/settings/device.enum";
import { Monitor, User, Clock } from '@element-plus/icons-vue';
import ApplicationAPI, { type ApplicationForm, type ApplicationInfo, type ApplicationPageQuery } from "@/api/application/myapp";
import ApplicationAPI, { type ApplicationForm, type ApplicationInfo, type ApplicationPageQuery } from "@/api/module_application/myapp";
import { formatToDateTime } from "@/utils/dateUtil";
const appStore = useAppStore();
@@ -203,7 +203,7 @@ defineOptions({
});
import { ref, reactive, onMounted } from "vue";
import ExampleAPI, { ExampleTable, ExampleForm, ExamplePageQuery } from "@/api/generator/demo";
import ExampleAPI, { ExampleTable, ExampleForm, ExamplePageQuery } from "@/api/module_generator/demo";
import DatePicker from "@/components/DatePicker/index.vue";
import { formatToDateTime } from "@/utils/dateUtil";
@@ -43,7 +43,7 @@
<el-button v-hasPerm="['generator:gencode:delete']" type="danger" plain icon="Delete" :disabled="ids.length === 0" @click="handleDelete()">批量删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button v-hasPerm="['generator:gencode:code']" type="warning" plain icon="Download" :disabled="!canGenerate" @click="handleGenTable()">批量生成</el-button>
<el-button v-hasPerm="['generator:gencode:code']" type="warning" plain icon="Download" :disabled="!canGenerate" @click="handleGenTable('0')">批量生成</el-button>
</el-col>
</el-row>
</div>
@@ -118,11 +118,31 @@
<!-- 创建表 -->
<el-dialog v-model="createTableVisible" title="创建表" append-to-body >
<span>创建表语句(支持多个建表sql语句)</span>
<el-input v-model="createContent" type="textarea" :rows="10" placeholder="请输入创建表sql语句" clearable :rules="[{ required: true, message: '请输入创建表sql语句', trigger: 'blur' }]"></el-input>
<el-button type="warning" size="small" class="ml-1 mb-1" @click="loadExampleMysql">加载MySQL示例</el-button>
<el-button type="primary" size="small" class="ml-1 mb-1" @click="loadExamplePostgres">加载Postgres示例</el-button>
<el-scrollbar max-height="72vh">
<div class="absolute z-36 right-5 top-2">
<el-link type="primary" @click="handleCopyCode">
<el-icon>
<CopyDocument />
</el-icon>
复制代码
</el-link>
</div>
<Codemirror
ref="sqlRef"
v-model:value="createContent"
:options="sqlOptions"
border
:height="'300px'"
width="100%"
/>
</el-scrollbar>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" :loading="loading" @click="handleCreateTable(createContent)"> </el-button>
<el-button @click="createTableVisible = false"> </el-button>
<el-button @click="handleCreateTableCancel()"> </el-button>
</div>
</template>
</el-dialog>
@@ -154,12 +174,12 @@
</el-form-item>
</el-form>
<el-row>
<el-table ref="table" :data="dbTableList" height="300px" @row-click="clickRow" @selection-change="handleImportTableSelectionChange">
<el-table ref="table" :data="dbTableList" height="300px" border @row-click="clickRow" @selection-change="handleImportTableSelectionChange">
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column type="selection" width="55"></el-table-column>
<el-table-column label="序号" type="index" min-width="55" align="center" fixed>
<el-table-column label="序号" type="index" min-width="30" align="center" fixed>
<template #default="scope">
<span>{{(importQueryFormData.page_no - 1) * importQueryFormData.page_size + scope.$index + 1}}</span>
</template>
@@ -185,12 +205,11 @@
</el-dialog>
<!-- 代码生成抽屉 -->
<el-drawer v-model="editVisible" :title="'【代码生成】' + info.table_name" size="80%" @close="handleClose">
<el-drawer v-model="editVisible" :title="'【代码生成】' + info.table_name" size="85%" @close="handleClose">
<el-steps :active="activeStep" finish-status="success" simple>
<el-step title="基础配置" />
<el-step title="字段配置" />
<el-step title="预览代码" />
<el-step title="代码生成" />
</el-steps>
<div class="mt-5">
@@ -212,6 +231,68 @@
<el-input v-model="info.class_name" placeholder="请输入"/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="package_name">
<template #label>
包名
<el-tooltip content="生成在哪个python模块下,例如 module_gencode" placement="top">
<el-icon><QuestionFilled/></el-icon>
</el-tooltip>
</template>
<el-input v-model="info.package_name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="module_name">
<template #label>
模块名
<el-tooltip content="可理解为子系统名,例如 system" placement="top">
<el-icon><QuestionFilled/></el-icon>
</el-tooltip>
</template>
<el-input v-model="info.module_name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="business_name">
<template #label>
业务名
<el-tooltip content="可理解为功能英文名,例如 user" placement="top">
<el-icon><QuestionFilled/></el-icon>
</el-tooltip>
</template>
<el-input v-model="info.business_name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="function_name">
<template #label>
功能名
<el-tooltip content="用作类描述,例如 用户" placement="top">
<el-icon><QuestionFilled/></el-icon>
</el-tooltip>
</template>
<el-input v-model="info.function_name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
上级菜单
<el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
<el-icon><QuestionFilled/></el-icon>
</el-tooltip>
</template>
<el-tree-select
v-model="info.parent_menu_id"
:data="menuOptions"
placeholder="请选择系统菜单"
check-strictly
filterable
:render-after-expand="false"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="description">
<el-input v-model="info.description" type="textarea" :rows="3"></el-input>
@@ -266,7 +347,7 @@
<el-table
ref="dragTable"
v-loading="loading"
:data="columns"
:data="info.columns"
row-key="id"
max-height="680"
highlight--currentrow
@@ -277,7 +358,7 @@
<template #empty>
<el-empty :image-size="80" description="暂无数据" />
</template>
<el-table-column label="序号" type="index" min-width="5%" fixed />
<el-table-column label="序号" type="index" min-width="7%" fixed />
<el-table-column label="列名" prop="column_name" min-width="10%" :show-overflow-tooltip="true"/>
<el-table-column label="类型" prop="column_type" min-width="10%" :show-overflow-tooltip="true"/>
<el-table-column label="长度" prop="column_length" min-width="8%" :show-overflow-tooltip="true">
@@ -355,9 +436,9 @@
<el-checkbox v-model="scope.row.is_increment" true-value="1" false-value="0" />
</template>
</el-table-column>
<el-table-column label="必填" min-width="10%">
<el-table-column label="允许空" min-width="10%">
<template #default="scope">
<el-checkbox v-model="scope.row.is_required" true-value="1" false-value="0"></el-checkbox>
<el-checkbox v-model="scope.row.is_nullable" true-value="0" false-value="1"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="唯一" min-width="10%">
@@ -365,6 +446,11 @@
<el-checkbox v-model="scope.row.is_unique" true-value="1" false-value="0"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="主键" min-width="10%">
<template #default="scope">
<el-checkbox v-model="scope.row.is_pk" true-value="1" false-value="0"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="表单类型" min-width="12%">
<template #default="scope">
<el-select v-model="scope.row.html_type">
@@ -454,120 +540,16 @@
</el-scrollbar>
</el-col>
</el-row>
<!-- 第四步代码生成 -->
<el-form v-show="activeStep == 3" ref="genInfo" :model="info" :rules="rules" label-width="150px">
<el-row>
<el-col :span="12">
<el-form-item prop="package_name">
<template #label>
生成包路径
<el-tooltip content="生成在哪个python模块下,例如 module_gencode" placement="top">
<el-icon><QuestionFilled/></el-icon>
</el-tooltip>
</template>
<el-input v-model="info.package_name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="module_name">
<template #label>
生成模块名
<el-tooltip content="可理解为子系统名,例如 system" placement="top">
<el-icon><QuestionFilled/></el-icon>
</el-tooltip>
</template>
<el-input v-model="info.module_name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="business_name">
<template #label>
生成业务名
<el-tooltip content="可理解为功能英文名,例如 user" placement="top">
<el-icon><QuestionFilled/></el-icon>
</el-tooltip>
</template>
<el-input v-model="info.business_name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="function_name">
<template #label>
生成功能名
<el-tooltip content="用作类描述,例如 用户" placement="top">
<el-icon><QuestionFilled/></el-icon>
</el-tooltip>
</template>
<el-input v-model="info.function_name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="gen_type">
<template #label>
生成代码方式
<el-tooltip content="默认为zip压缩包下载,也可以自定义生成路径" placement="top">
<el-icon><QuestionFilled/></el-icon>
</el-tooltip>
</template>
<el-radio v-model="info.gen_type" value="0">zip压缩包</el-radio>
<el-radio v-model="info.gen_type" value="1">项目目录</el-radio>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
上级菜单
<el-tooltip content="分配到指定菜单下,例如 系统管理" placement="top">
<el-icon><QuestionFilled/></el-icon>
</el-tooltip>
</template>
<el-tree-select
v-model="info.parent_menu_id"
:data="menuOptions"
placeholder="请选择系统菜单"
check-strictly
filterable
:render-after-expand="false"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<template #footer>
<!-- 公共按钮关闭 -->
<el-button :icon="Close" @click="close">关闭</el-button>
<!-- 第一步只有下一步 -->
<el-button v-if="activeStep === 0" type="primary" @click="nextStep">
下一步字段配置<el-icon class="el-icon--right"><Right /></el-icon>
</el-button>
<!-- 第二步上一步下一步 -->
<el-button v-if="activeStep === 1" type="success" :icon="Back" @click="prevStep">上一步基础配置</el-button>
<el-button v-if="activeStep === 1" v-hasPerm="['generator:gencode:update']" type="warning" :icon="Edit" @click="submitForm">保存字段配置</el-button>
<el-button v-if="activeStep === 1" type="primary" @click="nextStep">
下一步预览代码<el-icon class="el-icon--right"><View /></el-icon>
</el-button>
<!-- 第三步上一步下载代码 -->
<el-button v-if="activeStep === 2" type="success" :icon="Back" @click="prevStep">上一步字段配置</el-button>
<el-button v-if="activeStep === 2" type="primary" @click="nextStep">
下一步生成代码<el-icon class="el-icon--right"><Right /></el-icon>
</el-button>
<!-- 第四步上一步写入本地 -->
<el-button v-if="activeStep === 3" type="success" :icon="View" @click="prevStep">上一步预览代码</el-button>
<el-button v-if="activeStep === 3" :disabled="info.gen_type != '0'" type="warning" :icon="Download" :loading="loading" @click="handleGenTable(info)">下载代码</el-button>
<el-button v-if="activeStep === 3" :disabled="info.gen_type != '1'" type="primary" :icon="FolderOpened" :loading="loading" @click="handleGenTable(info)">写入本地</el-button>
<el-button v-if="activeStep != 0" type="success" :icon="Back" @click="prevStep">上一步</el-button>
<el-button v-if="activeStep != 2" v-hasPerm="['generator:gencode:update']" type="warning" :icon="Edit" :loading="loading" @click="submitForm">保存配置</el-button>
<el-button v-if="activeStep != 2" type="primary" @click="nextStep">下一步<el-icon class="el-icon--right"><Right /></el-icon></el-button>
<el-button v-if="activeStep === 2" type="warning" :icon="Download" :loading="loading" @click="handleGenTable('0', info)">下载代码</el-button>
<el-button v-if="activeStep === 2" type="primary" :icon="FolderOpened" :loading="loading" @click="handleGenTable('1', info)">写入本地</el-button>
</template>
</el-drawer>
</div>
@@ -580,6 +562,7 @@ defineOptions({
});
import "codemirror/mode/javascript/javascript.js";
import "codemirror/mode/sql/sql.js";
import { ref, reactive, computed, onActivated, onMounted } from 'vue';
import { useClipboard } from '@vueuse/core';
import { useRoute } from 'vue-router';
@@ -587,10 +570,10 @@ import Codemirror from "codemirror-editor-vue3";
import type { EditorConfiguration } from "codemirror";
import type { CmComponentRef } from "codemirror-editor-vue3";
import { ElMessage, ElMessageBox, type FormInstance, type TableInstance } from 'element-plus';
import { QuestionFilled, MagicStick, View, CopyDocument, Close, Right, FolderOpened, Back, Download, Edit } from '@element-plus/icons-vue';
import GencodeAPI, { type GenTableOutVO, type DatabaseTable, type GenTableQueryParam, type GenTableColumnOutSchema, type GenTableSchema } from "@/api/generator/gencode";
import MenuAPI, { MenuTable } from "@/api/system/menu";
import DictAPI, { DictTable } from "@/api/system/dict";
import { QuestionFilled, MagicStick, CopyDocument, Close, Right, FolderOpened, Back, Download, Edit } from '@element-plus/icons-vue';
import GencodeAPI, { type GenTableSchema, type DBTableSchema, type GenTablePageQuery } from "@/api/module_generator/gencode";
import MenuAPI, { MenuTable } from "@/api/module_system/menu";
import DictAPI, { DictTable } from "@/api/module_system/dict";
import { formatTree } from "@/utils/common";
import { MenuTypeEnum } from "@/enums";
@@ -621,8 +604,8 @@ interface FileData {
const queryRef = ref<FormInstance>();
const table = ref<TableInstance>();
const cmRef = ref<CmComponentRef>();
const sqlRef = ref<CmComponentRef>();
const basicInfo = ref<FormInstance>();
const genInfo = ref<FormInstance>();
const dragTable = ref<TableInstance>();
//
@@ -639,15 +622,15 @@ const importVisible = ref(false);
//
const createContent = ref("");
const dateRange = ref<[Date, Date] | []>([]);
const tableList = ref<GenTableOutVO[]>([]);
const dbTableList = ref<DatabaseTable[]>([]);
const tableList = ref<GenTableSchema[]>([]);
const dbTableList = ref<DBTableSchema[]>([]);
const ids = ref<number[]>([]);
const tableNames = ref<string[]>([]);
//
const importLoading = ref(false);
const importTotal = ref<number>(0);
const importQueryFormData = reactive<GenTableQueryParam>({
const importQueryFormData = reactive<GenTablePageQuery>({
page_no: 1,
page_size: 10,
table_name: undefined,
@@ -664,7 +647,6 @@ type TableItem = {
table_comment: string;
};
const tables = ref<TableItem[]>([]);
const columns = ref<GenTableColumnOutSchema[]>([]);
//
async function handleImportClick() {
@@ -687,13 +669,11 @@ const code = ref<string>('');
const treeData = ref<TreeNode[]>([]);
//
const queryFormData = reactive<GenTableQueryParam>({
const queryFormData = reactive<GenTablePageQuery>({
page_no: 1,
page_size: 10,
table_name: undefined,
table_comment: undefined,
start_time: undefined,
end_time: undefined,
});
//
@@ -718,6 +698,17 @@ const cmOptions: EditorConfiguration = {
readOnly: true
};
const sqlOptions: EditorConfiguration = {
mode: "text/x-sql",
lineNumbers: true,
smartIndent: true,
indentUnit: 2,
tabSize: 2,
readOnly: false,
theme: "default",
lineWrapping: true,
};
//
const { copy } = useClipboard();
@@ -831,7 +822,7 @@ function buildTree(data: FileData[]): TreeNode {
}
/** 获取生成预览 */
async function handlePreview(row: GenTableOutVO): Promise<void> {
async function handlePreview(row: GenTableSchema): Promise<void> {
try {
if (!row.id) {
ElMessage.warning('无效的表ID');
@@ -906,14 +897,12 @@ async function loadingData(): Promise<void> {
}
/** 表格行内生成代码操作 */
async function handleGenTable(row?: GenTableOutVO): Promise<void> {
async function handleGenTable(targetGenType: string, row?: GenTableSchema): Promise<void> {
let tbNames: string | string[] = [];
let targetGenType = '0';
//
if (row) {
tbNames = [row.table_name || ''];
targetGenType = row.gen_type || '0';
} else if (tableNames.value.length > 0) {
tbNames = tableNames.value;
} else {
@@ -954,7 +943,7 @@ async function handleGenTable(row?: GenTableOutVO): Promise<void> {
}
/** 同步数据库操作 */
async function handleSynchDb(row: GenTableOutVO): Promise<void> {
async function handleSynchDb(row: GenTableSchema): Promise<void> {
const tableName = row.table_name || '';
if (!tableName) {
@@ -997,27 +986,19 @@ async function handleRefresh() {
}
/** 多选框选中数据 - 主表格 */
function handleTableSelectionChange(selection: GenTableOutVO[]): void {
function handleTableSelectionChange(selection: GenTableSchema[]): void {
ids.value = selection.map((item) => item.id!);
tableNames.value = selection.map((item) => item.table_name || '').filter(Boolean);
}
/** 多选框选中数据 - 导入表格 */
function handleImportTableSelectionChange(selection: DatabaseTable[]): void {
function handleImportTableSelectionChange(selection: DBTableSchema[]): void {
tables.value = selection.map(item => ({
table_name: item.table_name || '',
table_comment: item.table_comment || ''
}));
}
/** 表格行内修改按钮操作 */
/** 计算表格高度 */
function calculateTableHeight() {
//
// 使400px
}
//
const filterMenuTypes = (nodes: MenuTable[]) => {
return nodes
@@ -1029,7 +1010,7 @@ const filterMenuTypes = (nodes: MenuTable[]) => {
};
/** 表格行内修改按钮操作 */
async function handlePreviewTable(row?: GenTableOutVO): Promise<void> {
async function handlePreviewTable(row?: GenTableSchema): Promise<void> {
const selectedTableId = row?.id || ids.value[0];
if (selectedTableId) {
// ID
@@ -1043,17 +1024,13 @@ async function handlePreviewTable(row?: GenTableOutVO): Promise<void> {
const dict_response = await DictAPI.getDictTypeList({page_no: 1, page_size: 100});
dictOptions.value = dict_response.data.data.items;
// DOM
setTimeout(() => {
calculateTableHeight();
}, 100);
} else {
ElMessage.error('请选择要修改的数据');
}
}
/** 删除按钮操作 */
async function handleDelete(row?: GenTableOutVO): Promise<void> {
async function handleDelete(row?: GenTableSchema): Promise<void> {
const tableIds = row?.id ? [row.id] : ids.value;
if (tableIds.length === 0) {
@@ -1084,6 +1061,50 @@ async function handleDelete(row?: GenTableOutVO): Promise<void> {
}
}
/** 加载SQL示例 */
function loadExampleMysql(): void {
const exampleSql = `-- MySQL SQL案例
CREATE TABLE \`gen_demo01\` (
\`name\` varchar(64) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '名称',
\`status\` tinyint(1) NOT NULL COMMENT '是否启用(True:启用 False:禁用)',
\`creator_id\` int DEFAULT NULL COMMENT '创建人ID',
\`id\` int NOT NULL AUTO_INCREMENT COMMENT '主键ID',
\`description\` text COLLATE utf8mb4_unicode_ci COMMENT '备注/描述',
\`created_at\` datetime DEFAULT NULL COMMENT '创建时间',
\`updated_at\` datetime DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (\`id\`),
KEY \`ix_gen_demo01_creator_id\` (\`creator_id\`),
CONSTRAINT \`gen_demo01_ibfk_1\` FOREIGN KEY (\`creator_id\`) REFERENCES \`system_users\` (\`id\`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='示例表';`;
createContent.value = exampleSql;
}
function loadExamplePostgres(): void {
const exampleSql = `-- Psstgres SQL案例
CREATE TABLE gen_demo01(
name varchar(64),
status boolean NOT NULL,
creator_id integer,
id SERIAL NOT NULL,
description text,
created_at timestamp without time zone,
updated_at timestamp without time zone,
PRIMARY KEY(id),
CONSTRAINT gen_demo01_creator_id_fkey FOREIGN key(creator_id) REFERENCES system_users(id)
);
CREATE INDEX ix_gen_demo01_creator_id ON public.gen_demo01 USING btree (creator_id);
COMMENT ON TABLE gen_demo01 IS '示例表';
COMMENT ON COLUMN gen_demo01.name IS '名称';
COMMENT ON COLUMN gen_demo01.status IS '是否启用(True:启用 False:禁用)';
COMMENT ON COLUMN gen_demo01.creator_id IS '创建人ID';
COMMENT ON COLUMN gen_demo01.id IS '主键ID';
COMMENT ON COLUMN gen_demo01.description IS '备注/描述';
COMMENT ON COLUMN gen_demo01.created_at IS '创建时间';
COMMENT ON COLUMN gen_demo01.updated_at IS '更新时间';`;
createContent.value = exampleSql;
}
/** 创建表操作 */
async function handleCreateTable(sql: string): Promise<void> {
if (!sql || sql.trim() === '') {
@@ -1104,6 +1125,14 @@ async function handleCreateTable(sql: string): Promise<void> {
}
}
/** 取消创建表操作 */
function handleCreateTableCancel(): void {
createTableVisible.value = false;
createContent.value = '';
}
/** 导入表操作 */
async function handleImportTable(): Promise<void> {
if (tables.value.length === 0) {
@@ -1126,7 +1155,7 @@ async function handleImportTable(): Promise<void> {
}
/** 单击选择行 */
function clickRow(row: DatabaseTable): void {
function clickRow(row: DBTableSchema): void {
table.value?.toggleRowSelection(row);
}
@@ -1195,11 +1224,8 @@ const info = reactive<GenTableSchema>({
module_name: '',
business_name: '',
function_name: '',
gen_type: '0',
options: {parent_menu_id: undefined,},
description: '',
parent_menu_id: undefined,
parent_menu_name: '',
pk_column: undefined,
sub_table: undefined,
columns: [],
@@ -1263,7 +1289,7 @@ async function nextStep(): Promise<void> {
//
if (activeStep.value === 2 && info.id) {
await handlePreview({ id: info.id, table_name: info.table_name } as GenTableOutVO);
await handlePreview({ id: info.id, table_name: info.table_name } as GenTableSchema);
}
}
}
@@ -1297,7 +1323,6 @@ function close(): void {
//
setTimeout(() => {
basicInfo.value?.resetFields();
genInfo.value?.resetFields();
}, 300);
}
@@ -1316,20 +1341,16 @@ async function loadTableDetail(id: number | string) {
const data = response.data.data;
//
Object.assign(info, { ...data.info });
Object.assign(info, { ...data });
//
if (data.rows) {
if (data && data.columns && Array.isArray(data.columns)) {
//
info.columns = JSON.parse(JSON.stringify(data.rows));
info.columns = JSON.parse(JSON.stringify(data.columns));
//
if (info.columns) {
info.columns.forEach((item: any) => {
item.select = true;
});
//
columns.value = [...info.columns];
}
}
//
@@ -266,7 +266,7 @@ defineOptions({
import { ref, reactive, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { ResultEnum } from "@/enums/api/result.enum";
import ExampleAPI, { ExampleTable, ExampleForm, ExamplePageQuery } from "@/api/generator/demo";
import ExampleAPI, { ExampleTable, ExampleForm, ExamplePageQuery } from "@/api/module_generator/demo";
import ImportModal from "@/components/CURD/ImportModal.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import DatePicker from "@/components/DatePicker/index.vue";
@@ -235,7 +235,7 @@
</template>
<script lang="ts" setup>
import CacheAPI, { type CacheInfo, type CacheForm, type CacheMonitor, type RedisInfo } from "@/api/monitor/cache";
import CacheAPI, { type CacheInfo, type CacheForm, type CacheMonitor, type RedisInfo } from "@/api/module_monitor/cache";
import * as echarts from 'echarts';
//
@@ -111,7 +111,7 @@ defineOptions({
inheritAttrs: false,
});
import OnlineAPI, { type OnlineUserPageQuery, type OnlineUserTable } from "@/api/monitor/online";
import OnlineAPI, { type OnlineUserPageQuery, type OnlineUserTable } from "@/api/module_monitor/online";
const queryFormRef = ref();
const total = ref(0);
@@ -287,7 +287,7 @@ import {
UploadFilled,
QuestionFilled
} from '@element-plus/icons-vue'
import { ResourceAPI, type ResourceItem, type ResourcePageQuery } from '@/api/monitor/resource'
import { ResourceAPI, type ResourceItem, type ResourcePageQuery } from '@/api/module_monitor/resource'
//
const fileList = ref<ResourceItem[]>([])
@@ -231,7 +231,7 @@
</template>
<script lang="ts" setup>
import ServerAPI, {type ServerInfo } from '@/api/monitor/server'
import ServerAPI, {type ServerInfo } from '@/api/module_monitor/server'
const loading = ref(false);
@@ -104,7 +104,7 @@ import type { FormInstance } from "element-plus";
import { LocationQuery, RouteLocationRaw, useRoute, useRouter } from "vue-router";
import { useI18n } from "vue-i18n";
import { onActivated, onMounted, watch } from "vue";
import AuthAPI, {type LoginFormData, type CaptchaInfo } from "@/api/system/auth";
import AuthAPI, {type LoginFormData, type CaptchaInfo } from "@/api/module_system/auth";
import { useAppStore, useUserStore, useSettingsStore } from "@/store";
import CommonWrapper from "@/components/CommonWrapper/index.vue";
@@ -73,7 +73,7 @@
<script setup lang="ts">
import type { FormInstance } from "element-plus";
import { Lock } from "@element-plus/icons-vue";
import UserAPI, { type RegisterForm } from "@/api/system/user";
import UserAPI, { type RegisterForm } from "@/api/module_system/user";
import { useConfigStore } from "@/store";
import { useI18n } from "vue-i18n";
@@ -66,7 +66,7 @@
<script setup lang="ts">
import { useI18n } from "vue-i18n";
import type { FormInstance } from "element-plus";
import UserAPI, { type ForgetPasswordForm } from "@/api/system/user";
import UserAPI, { type ForgetPasswordForm } from "@/api/module_system/user";
const loading = ref(false); // loading
const isCapsLock = ref(false); //
@@ -199,7 +199,7 @@ defineOptions({
inheritAttrs: false,
});
import DeptAPI, { DeptTable, DeptForm, DeptPageQuery } from "@/api/system/dept";
import DeptAPI, { DeptTable, DeptForm, DeptPageQuery } from "@/api/module_system/dept";
import { useUserStore } from "@/store";
import { formatTree } from "@/utils/common";
import { formatToDateTime } from "@/utils/dateUtil";
@@ -276,10 +276,10 @@ const props = defineProps({
})
const drawerVisible = defineModel<boolean>()
import DictAPI, { DictDataTable, DictDataForm, DictDataPageQuery } from "@/api/system/dict";
import DictAPI, { DictDataTable, DictDataForm, DictDataPageQuery } from "@/api/module_system/dict";
import { useAppStore } from "@/store/modules/app.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
import UserTableSelect from "@/views/module_system/user/components/UserTableSelect.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import type { IContentConfig } from "@/components/CURD/types";
import { formatToDateTime } from "@/utils/dateUtil";
@@ -234,9 +234,9 @@ defineOptions({
inheritAttrs: false,
});
import DictAPI, { DictTable, DictForm, DictPageQuery } from "@/api/system/dict";
import DataDrawer from "@/views/system/dict/components/DataDrawer.vue"
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
import DictAPI, { DictTable, DictForm, DictPageQuery } from "@/api/module_system/dict";
import DataDrawer from "@/views/module_system/dict/components/DataDrawer.vue"
import UserTableSelect from "@/views/module_system/user/components/UserTableSelect.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import type { IContentConfig } from "@/components/CURD/types";
import { formatToDateTime } from "@/utils/dateUtil";
@@ -216,8 +216,8 @@ defineOptions({
inheritAttrs: false,
});
import LogAPI, { LogTable, LogPageQuery } from "@/api/system/log";
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
import LogAPI, { LogTable, LogPageQuery } from "@/api/module_system/log";
import UserTableSelect from "@/views/module_system/user/components/UserTableSelect.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import JsonPretty from "@/components/JsonPretty/index.vue";
import type { IContentConfig } from "@/components/CURD/types";
@@ -482,7 +482,7 @@ import { useAppStore } from "@/store/modules/app.store";
import { useUserStore } from "@/store/modules/user.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
import MenuAPI, { MenuPageQuery, MenuForm, MenuTable } from "@/api/system/menu";
import MenuAPI, { MenuPageQuery, MenuForm, MenuTable } from "@/api/module_system/menu";
import { MenuTypeEnum } from "@/enums/system/menu.enum";
import { formatTree } from "@/utils/common";
import { formatToDateTime } from "@/utils/dateUtil";
@@ -267,7 +267,7 @@
<script setup lang="ts">
import { useDictStore } from "@/store/index";
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
import UserTableSelect from "@/views/module_system/user/components/UserTableSelect.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import type { IContentConfig } from "@/components/CURD/types";
import { formatToDateTime } from "@/utils/dateUtil";
@@ -278,7 +278,7 @@ defineOptions({
inheritAttrs: false,
});
import NoticeAPI, { NoticeTable, NoticeForm, NoticePageQuery } from "@/api/system/notice";
import NoticeAPI, { NoticeTable, NoticeForm, NoticePageQuery } from "@/api/module_system/notice";
const queryFormRef = ref();
const dataFormRef = ref();
@@ -254,7 +254,7 @@
<script lang="ts" setup>
import { ref, reactive, onMounted, computed } from 'vue';
import ParamsAPI, { type ConfigTable } from '@/api/system/params';
import ParamsAPI, { type ConfigTable } from '@/api/module_system/params';
import { useConfigStore } from "@/store";
import { useI18n } from 'vue-i18n';
import { ElMessage, ElMessageBox } from 'element-plus';
@@ -210,8 +210,8 @@ defineOptions({
inheritAttrs: false,
});
import ParamsAPI, { ConfigTable, ConfigForm, ConfigPageQuery } from "@/api/system/params";
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
import ParamsAPI, { ConfigTable, ConfigForm, ConfigPageQuery } from "@/api/module_system/params";
import UserTableSelect from "@/views/module_system/user/components/UserTableSelect.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import type { IContentConfig } from "@/components/CURD/types";
import { formatToDateTime } from "@/utils/dateUtil";
@@ -218,9 +218,9 @@ defineOptions({
inheritAttrs: false,
});
import PositionAPI, { PositionTable, PositionForm, PositionPageQuery } from "@/api/system/position";
import PositionAPI, { PositionTable, PositionForm, PositionPageQuery } from "@/api/module_system/position";
import { useUserStore } from "@/store";
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
import UserTableSelect from "@/views/module_system/user/components/UserTableSelect.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import type { IContentConfig } from "@/components/CURD/types";
import { formatToDateTime } from "@/utils/dateUtil";
@@ -146,9 +146,9 @@ const props = defineProps({
})
import { listToTree, formatTree } from "@/utils/common";
import RoleAPI, { permissionDataType, permissionDeptType, permissionMenuType } from "@/api/system/role";
import DeptAPI from "@/api/system/dept";
import MenuAPI from "@/api/system/menu";
import RoleAPI, { permissionDataType, permissionDeptType, permissionMenuType } from "@/api/module_system/role";
import DeptAPI from "@/api/module_system/dept";
import MenuAPI from "@/api/module_system/menu";
import type { TreeInstance } from 'element-plus'
import { useAppStore } from "@/store/modules/app.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
@@ -285,9 +285,9 @@ defineOptions({
});
import { ElMessage, ElMessageBox } from "element-plus";
import RoleAPI, { RoleTable, RoleForm, TablePageQuery } from "@/api/system/role";
import RoleAPI, { RoleTable, RoleForm, TablePageQuery } from "@/api/module_system/role";
import { useUserStore } from "@/store";
import UserTableSelect from "@/views/system/user/components/UserTableSelect.vue";
import UserTableSelect from "@/views/module_system/user/components/UserTableSelect.vue";
import ExportModal from "@/components/CURD/ExportModal.vue";
import type { IContentConfig } from "@/components/CURD/types";
import { QuestionFilled, ArrowUp, ArrowDown } from "@element-plus/icons-vue";
@@ -26,7 +26,7 @@
</template>
<script setup lang="ts">
import DeptAPI, { DeptPageQuery } from "@/api/system/dept";
import DeptAPI, { DeptPageQuery } from "@/api/module_system/dept";
import { formatTree } from "@/utils/common";
import type { FilterNodeMethodFunction, TreeInstance } from 'element-plus'
@@ -11,7 +11,7 @@
<script setup lang="ts">
import type { ISelectConfig } from "@/components/TableSelect/index.vue";
import UserAPI from "@/api/system/user";
import UserAPI from "@/api/module_system/user";
// ID
const props = defineProps<{ modelValue?: number }>();
@@ -369,11 +369,11 @@ import { useAppStore } from "@/store/modules/app.store";
import { DeviceEnum } from "@/enums/settings/device.enum";
import { ResultEnum } from "@/enums/api/result.enum";
import UserAPI, { type UserForm, type UserInfo, type UserPageQuery } from "@/api/system/user";
import UserAPI, { type UserForm, type UserInfo, type UserPageQuery } from "@/api/module_system/user";
import { formatTree } from "@/utils/common";
import PositionAPI from "@/api/system/position";
import DeptAPI from "@/api/system/dept";
import RoleAPI from "@/api/system/role";
import PositionAPI from "@/api/module_system/position";
import DeptAPI from "@/api/module_system/dept";
import RoleAPI from "@/api/module_system/role";
import { formatToDateTime } from "@/utils/dateUtil";
import DeptTree from "./components/DeptTree.vue";