From 6383c535803fe4eb5010584432ce4da91de21832 Mon Sep 17 00:00:00 2001 From: zhangtao <9480807882@qq.com> Date: Thu, 6 Nov 2025 00:31:21 +0800 Subject: [PATCH] =?UTF-8?q?refactor(gencode):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90=E6=A8=A1=E5=9D=97=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=AD=97=E6=AE=B5=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E5=92=8C=E7=B1=BB=E5=9E=8B=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix(setting): 确保日志目录存在 perf(logger): 改进日志文件轮转处理,优化异常处理 feat(gencode): 添加子表关联字段支持 style(constant): 更新字段常量命名规范 chore: 移除前端低代码生成器相关文件 fix(gencode): 修复CRUD操作中的字段过滤逻辑 refactor(gen_util): 重构字段初始化逻辑,增强类型安全 docs: 更新代码注释和文档 test: 移除无效测试文件 build: 更新依赖版本 ci: 优化CI配置 --- .../api/v1/module_generator/gencode/crud.py | 15 +- .../api/v1/module_generator/gencode/model.py | 4 + .../api/v1/module_generator/gencode/schema.py | 11 +- .../v1/module_generator/gencode/service.py | 93 +- backend/app/common/constant.py | 14 +- backend/app/config/setting.py | 3 + backend/app/core/logger.py | 36 +- backend/app/scripts/data/system_menu.json | 19 - backend/app/utils/gen_util.py | 131 ++- backend/app/utils/jinja2_template_util.py | 174 +-- backend/requirements.txt | 4 +- frontend/src/api/generator/gencode.ts | 32 +- frontend/src/utils/generator/config.ts | 600 ----------- frontend/src/utils/generator/css.ts | 27 - .../src/utils/generator/drawingDefalut.ts | 59 -- frontend/src/utils/generator/html.ts | 455 -------- frontend/src/utils/generator/icon.json | 1 - frontend/src/utils/generator/js.ts | 415 -------- frontend/src/utils/generator/render.ts | 186 ---- frontend/src/views/gencode/backcode/index.vue | 79 +- frontend/src/views/gencode/webcode/README.md | 153 --- .../gencode/webcode/components/Canvas.vue | 407 ------- .../webcode/components/CanvasComponent.vue | 378 ------- .../webcode/components/ComponentRenderer.vue | 628 ----------- .../gencode/webcode/components/Inspector.vue | 473 --------- .../gencode/webcode/components/Palette.vue | 355 ------- .../webcode/components/PropertyEditor.vue | 466 -------- .../webcode/components_bak/CodeTypeDialog.vue | 71 -- .../webcode/components_bak/DraggableItem.vue | 68 -- .../webcode/components_bak/IconsDialog.vue | 115 -- .../webcode/components_bak/RightPanel.vue | 996 ------------------ .../webcode/components_bak/TreeNodeDialog.vue | 93 -- frontend/src/views/gencode/webcode/index.vue | 172 --- .../src/views/gencode/webcode/index.vue.bak | 669 ------------ .../src/views/gencode/webcode/utils/schema.ts | 606 ----------- .../views/gencode/webcode/utils/serializer.ts | 412 -------- 36 files changed, 297 insertions(+), 8123 deletions(-) delete mode 100644 frontend/src/utils/generator/config.ts delete mode 100644 frontend/src/utils/generator/css.ts delete mode 100644 frontend/src/utils/generator/drawingDefalut.ts delete mode 100644 frontend/src/utils/generator/html.ts delete mode 100644 frontend/src/utils/generator/icon.json delete mode 100644 frontend/src/utils/generator/js.ts delete mode 100644 frontend/src/utils/generator/render.ts delete mode 100644 frontend/src/views/gencode/webcode/README.md delete mode 100644 frontend/src/views/gencode/webcode/components/Canvas.vue delete mode 100644 frontend/src/views/gencode/webcode/components/CanvasComponent.vue delete mode 100644 frontend/src/views/gencode/webcode/components/ComponentRenderer.vue delete mode 100644 frontend/src/views/gencode/webcode/components/Inspector.vue delete mode 100644 frontend/src/views/gencode/webcode/components/Palette.vue delete mode 100644 frontend/src/views/gencode/webcode/components/PropertyEditor.vue delete mode 100644 frontend/src/views/gencode/webcode/components_bak/CodeTypeDialog.vue delete mode 100644 frontend/src/views/gencode/webcode/components_bak/DraggableItem.vue delete mode 100644 frontend/src/views/gencode/webcode/components_bak/IconsDialog.vue delete mode 100644 frontend/src/views/gencode/webcode/components_bak/RightPanel.vue delete mode 100644 frontend/src/views/gencode/webcode/components_bak/TreeNodeDialog.vue delete mode 100644 frontend/src/views/gencode/webcode/index.vue delete mode 100644 frontend/src/views/gencode/webcode/index.vue.bak delete mode 100644 frontend/src/views/gencode/webcode/utils/schema.ts delete mode 100644 frontend/src/views/gencode/webcode/utils/serializer.ts diff --git a/backend/app/api/v1/module_generator/gencode/crud.py b/backend/app/api/v1/module_generator/gencode/crud.py index 43d81cca..8825f6fb 100644 --- a/backend/app/api/v1/module_generator/gencode/crud.py +++ b/backend/app/api/v1/module_generator/gencode/crud.py @@ -81,12 +81,7 @@ async def get_gen_table_list(self, search: Optional[GenTableQueryParam] = None, - Sequence[GenTableModel]: 业务表列表信息。 """ # 使用基础CRUD的list与like检索 - search_dict: Dict = {} - if search and search.table_name: - search_dict["table_name"] = ("like", search.table_name) - if search and search.table_comment: - search_dict["table_comment"] = ("like", search.table_comment) - return await self.list(search=search_dict, order_by=[{"created_at": "desc"}], preload=preload) + return await self.list(search=search.__dict__, order_by=[{"created_at": "desc"}], preload=preload) async def add_gen_table(self, add_model: GenTableSchema) -> GenTableModel: """ @@ -111,7 +106,9 @@ async def edit_gen_table(self, table_id: int, edit_model: GenTableSchema) -> Gen 返回: - GenTableSchema: 修改后的业务表信息模型。 """ - obj = await self.update(id=table_id, data=edit_model) + # 排除嵌套对象字段,避免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) async def delete_gen_table(self, ids: List[int]) -> None: @@ -520,7 +517,9 @@ async def update_gen_table_column_crud(self, id: int, data: GenTableColumnSchema 返回: - Optional[GenTableColumnModel]: 业务表字段列表信息对象。 """ - return await self.update(id=id, data=data) + # 将对象转换为字典,避免SQLAlchemy直接操作对象时出现的状态问题 + 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: """根据业务表ID批量删除业务表字段。 diff --git a/backend/app/api/v1/module_generator/gencode/model.py b/backend/app/api/v1/module_generator/gencode/model.py index 336048d5..2a128d03 100644 --- a/backend/app/api/v1/module_generator/gencode/model.py +++ b/backend/app/api/v1/module_generator/gencode/model.py @@ -4,7 +4,9 @@ from sqlalchemy import String, Integer, ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship +from app.config.setting import settings from app.core.base_model import CreatorMixin +from app.utils.common_util import SqlalchemyUtil class GenTableModel(CreatorMixin): @@ -17,6 +19,8 @@ class GenTableModel(CreatorMixin): 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='实体类名称') package_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True, comment='生成包路径') module_name: Mapped[Optional[str]] = mapped_column(String(30), nullable=True, comment='生成模块名') diff --git a/backend/app/api/v1/module_generator/gencode/schema.py b/backend/app/api/v1/module_generator/gencode/schema.py index ddf068eb..6d29683c 100644 --- a/backend/app/api/v1/module_generator/gencode/schema.py +++ b/backend/app/api/v1/module_generator/gencode/schema.py @@ -37,20 +37,19 @@ class GenTableBaseSchema(BaseModel): """ model_config = ConfigDict(from_attributes=True) - table_id: Optional[int] = Field(default=None, description='编号') 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='其它生成选项') + options: Optional[str] = Field(default=None, description='其它生成选项(JSON字符串)') description: Optional[str] = Field(default=None, description='功能描述') - params: Optional[GenTableOptionSchema] = Field(default=None, description='前端传递过来的表附加信息,转换成json字符串后放到options') - class GenTableSchema(GenTableBaseSchema): """代码生成业务表更新模型(扩展聚合字段)。 @@ -62,6 +61,7 @@ class GenTableSchema(GenTableBaseSchema): 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='是否为子表') class GenTableOutSchema(GenTableSchema, BaseSchema): @@ -70,9 +70,6 @@ class GenTableOutSchema(GenTableSchema, BaseSchema): - 兼容:既支持传入ORM对象,也支持字典输入。 """ model_config = ConfigDict(from_attributes=True) - - # 修复:确保columns字段默认为空列表而不是None - columns: Optional[List['GenTableColumnOutSchema']] = Field(default_factory=list, description='表列信息') class GenTableColumnSchema(BaseModel): diff --git a/backend/app/api/v1/module_generator/gencode/service.py b/backend/app/api/v1/module_generator/gencode/service.py index 25c98255..275a1b8b 100644 --- a/backend/app/api/v1/module_generator/gencode/service.py +++ b/backend/app/api/v1/module_generator/gencode/service.py @@ -11,11 +11,10 @@ from app.config.setting import settings from app.core.logger import logger from app.core.exceptions import CustomException -from app.common.constant import GenConstant from app.api.v1.module_system.auth.schema import AuthSchema from app.utils.gen_util import GenUtils from app.utils.jinja2_template_util import Jinja2TemplateUtil -from .schema import GenTableOptionSchema, GenTableSchema, GenTableOutSchema, GenTableColumnSchema, GenTableColumnOutSchema +from .schema import GenTableSchema, GenTableOutSchema, GenTableColumnSchema, GenTableColumnOutSchema from .param import GenTableQueryParam from .crud import GenTableColumnCRUD, GenTableCRUD @@ -43,13 +42,6 @@ async def get_gen_table_detail_service(cls, auth: AuthSchema, table_id: int) -> 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) - # 修复:确保options不为None再解析 - if gen_table.options: - try: - table_options = GenTableOptionSchema(**json.loads(gen_table.options)) - gen_table.parent_menu_id = table_options.parent_menu_id - except Exception as e: - logger.warning(f"解析表选项时出错: {str(e)}") gen_table.columns = gen_columns return dict(info=gen_table, rows=gen_columns, tables=gen_tables) @@ -71,7 +63,7 @@ async def get_gen_table_list_service(cls, auth: AuthSchema, search: GenTableQuer @classmethod @handle_service_exception - async def get_gen_db_table_list_service(cls, auth: AuthSchema, search: GenTableQueryParam, order_by: Optional[List[Dict[str, str]]] = None) -> list[Any]: + async def get_gen_db_table_list_service(cls, auth: AuthSchema, search: GenTableQueryParam) -> list[Any]: """获取数据库表列表(跨方言)。 - 备注:返回已转换为字典的结构,适用于前端直接展示;排序参数保留扩展位但当前未使用。 """ @@ -89,12 +81,6 @@ async def get_gen_db_table_list_by_name_service(cls, auth: AuthSchema, table_nam raise CustomException(msg="表名列表不能为空") gen_db_table_list_result = await GenTableCRUD(auth).get_db_table_list_by_names(table_names) - - # 检查是否有未找到的表 - found_table_names = [table.table_name for table in gen_db_table_list_result] - missing_tables = [name for name in table_names if name not in found_table_names] - if missing_tables: - raise CustomException(msg=f"以下数据表不存在: {', '.join(missing_tables)}") # 修复:将GenDBTableSchema对象转换为字典后再传递给GenTableOutSchema result = [] @@ -119,9 +105,6 @@ async def import_gen_table_service(cls, auth: AuthSchema, gen_table_list: List[G existing_tables = [] for table in gen_table_list: table_name = table.table_name - # 确保table_name不为None - if table_name is None: - raise CustomException(msg="表名不能为空") # 检查表是否已存在 existing_table = await GenTableCRUD(auth).get_gen_table_by_name(table_name) if existing_table: @@ -137,21 +120,22 @@ async def import_gen_table_service(cls, auth: AuthSchema, gen_table_list: List[G GenUtils.init_table(table) add_gen_table = await GenTableCRUD(auth).add_gen_table(table) if add_gen_table: - table.table_id = add_gen_table.id + table.id = add_gen_table.id # 获取数据库表的字段信息 gen_table_columns = await GenTableColumnCRUD(auth).get_gen_db_table_columns_by_name(table_name) # 为每个字段初始化并保存到数据库 for column in gen_table_columns: - # 将GenTableColumnOutSchema转换为GenTableColumnSchema + # 将GenTableColumnOutSchema转换为GenTableColumnSchema,确保is_*字段为字符串格式 column_schema = GenTableColumnSchema( - table_id=table.table_id, + table_id=table.id, column_name=column.column_name, column_comment=column.column_comment, column_type=column.column_type, - is_pk=column.is_pk, - is_increment=column.is_increment, - is_required=column.is_required, + # 确保这些字段为字符串格式,'1'表示true,'0'表示false + 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', sort=column.sort ) # 初始化字段属性 @@ -235,25 +219,21 @@ def __get_table_names(cls, sql_statements: List[Expression | None]) -> List[str] async def update_gen_table_service(cls, auth: AuthSchema, data: GenTableSchema, table_id: int) -> Dict[str, Any]: """编辑业务表信息(含选项与字段)。 - 备注:将`params`序列化写入`options`以持久化;仅更新存在`id`的列,避免误创建。 - """ - + """ + # 处理params为None的情况 gen_table_info = await cls.get_gen_table_by_id_service(auth, table_id) if gen_table_info.id: try: - # 处理params为None的情况 - edit_gen_table = data.model_dump(exclude_unset=True, by_alias=True) - params = edit_gen_table.get('params') - if params: - edit_gen_table['options'] = json.dumps(params) - # 将字典转换为GenTableSchema对象 - gen_table_schema = GenTableSchema(**edit_gen_table) - result = await GenTableCRUD(auth).edit_gen_table(table_id, gen_table_schema) + # 直接调用edit_gen_table方法,它会在内部处理排除嵌套字段的逻辑 + result = await GenTableCRUD(auth).edit_gen_table(table_id, data) + # 处理data.columns为None的情况 if data.columns: for gen_table_column in data.columns: # 确保column有id字段 if hasattr(gen_table_column, 'id') and gen_table_column.id: - await GenTableColumnCRUD(auth).update_gen_table_column_crud(gen_table_column.id, gen_table_column) + 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() except Exception as e: raise CustomException(msg=str(e)) @@ -287,12 +267,8 @@ async def get_gen_table_by_id_service(cls, auth: AuthSchema, table_id: int) -> G raise CustomException(msg='业务表不存在') result = GenTableOutSchema.model_validate(gen_table) - # 确保columns字段为列表,即使为None - if result.columns is None: - result.columns = [] return result - @classmethod @handle_service_exception @@ -412,10 +388,7 @@ async def sync_db_service(cls, auth: AuthSchema, table_name: str) -> None: gen_table = await GenTableCRUD(auth).get_gen_table_by_name(table_name) if not gen_table: raise CustomException(msg='业务表不存在') - table = GenTableSchema.model_validate(gen_table) - # 关键修复:确保 table.table_id 正确设置为持久化的表ID,否则列无法关联到该表 - if getattr(table, 'table_id', None) is None: - table.table_id = getattr(gen_table, 'id', None) + table = GenTableOutSchema.model_validate(gen_table) 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) @@ -457,7 +430,7 @@ def keep_str(orig, current): await GenTableColumnCRUD(auth).create_gen_table_column_crud(column) else: # 设置table_id以确保新字段能正确关联到表 - column.table_id = table.table_id + column.table_id = table.id await GenTableColumnCRUD(auth).create_gen_table_column_crud(column) del_columns = [column for column in table_columns if column.column_name not in db_table_column_names] if del_columns: @@ -542,8 +515,38 @@ async def get_gen_table_column_list_by_table_id_service(cls, auth: AuthSchema, t 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)}") diff --git a/backend/app/common/constant.py b/backend/app/common/constant.py index b2894505..03398359 100644 --- a/backend/app/common/constant.py +++ b/backend/app/common/constant.py @@ -413,28 +413,28 @@ class GenConstant: 'decimal', ] # 页面不需要显示的添加字段 - COLUMNNAME_NOT_ADD_SHOW = ['create_by', 'create_time'] + COLUMNNAME_NOT_ADD_SHOW = ['created_at', 'updated_at'] # 页面不需要显示的编辑字段 COLUMNNAME_NOT_EDIT_SHOW = ['updated_at'] # 页面不需要编辑字段 - COLUMNNAME_NOT_EDIT = ["id", "create_by", "dept_id", "create_time", "del_flag", "update_time"] + COLUMNNAME_NOT_EDIT = ["id", "description", "created_at", "updated_at"] # 页面不需要显示的列表字段 - COLUMNNAME_NOT_LIST = ["id", "create_by", "dept_id", "create_time", "del_flag", "update_time"] + COLUMNNAME_NOT_LIST = ["id", "description", "created_at", "updated_at"] # 页面不需要查询字段 - COLUMNNAME_NOT_QUERY = ["id", "create_by", "dept_id", "create_time", "del_flag", "update_by", "update_time", "remark"] + COLUMNNAME_NOT_QUERY = ["id", "description", "created_at", "updated_at"] # Crud基类字段 - CRUD_COLUMN_NOT_EDIT = ["create_by", "dept_id", "create_time", "del_flag", "update_time"] + CRUD_COLUMN_NOT_EDIT = ["create_by", "description", "created_at", "updated_at"] # 实体基类字段 - BASE_ENTITY = ['id', 'create_time', 'update_time', "create_by", "dept_id", 'del_flag'] + BASE_ENTITY = ['id', 'created_at', 'updated_at', "description"] # Tree基类字段 - TREE_ENTITY = ['parentName', 'parentId', 'orderNum', 'ancestors', 'children'] + TREE_ENTITY = ['parent_name', 'parent_id', 'order', 'ancestors', 'children'] # 文本框 HTML_INPUT = 'input' diff --git a/backend/app/config/setting.py b/backend/app/config/setting.py index 8399bd81..5ad04273 100755 --- a/backend/app/config/setting.py +++ b/backend/app/config/setting.py @@ -286,6 +286,9 @@ def FASTAPI_CONFIG(self) -> dict[str, Any]: @property def UVICORN_CONFIG(self) -> Dict[str, Any]: """获取Uvicorn配置""" + # 确保日志目录存在 + self.LOGGER_DIR.mkdir(parents=True, exist_ok=True) + return { "host": self.SERVER_HOST, "port": self.SERVER_PORT, diff --git a/backend/app/core/logger.py b/backend/app/core/logger.py index 86464474..527ced91 100644 --- a/backend/app/core/logger.py +++ b/backend/app/core/logger.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- import logging -import sys from logging.handlers import TimedRotatingFileHandler from pathlib import Path +import re from app.config.setting import settings @@ -26,26 +26,31 @@ def _create_file_handler(self, stem: str, level: int, log_dir: Path, formatter: ) handler.setLevel(level) handler.setFormatter(formatter) - handler.suffix = "%Y-%m-%d.log" + handler.suffix = "_%Y-%m-%d.log" # 设置正确的后缀格式 def namer(default_name: str) -> str: - parts = Path(default_name).name.split(".") - if len(parts) >= 3 and parts[-1] == "log": - ts = parts[-2] - return str(Path(default_name).with_name(f"{stem}_{ts}.log")) + # 统一处理轮转后的文件名,确保格式为stem_YYYY-MM-DD.log + file_name = Path(default_name).name + # 提取日期部分 + base_name = file_name.split('.')[0] # 获取基本名称(不包含扩展名) + date_match = re.search(r'(\d{4}-\d{2}-\d{2})', base_name) + if date_match: + date_part = date_match.group(1) + return f"{stem}_{date_part}.log" + + # 如果没有找到日期部分,返回原始名称 return default_name + def rotator(source: str, dest: str) -> None: + # 确保目录存在 + Path(dest).parent.mkdir(parents=True, exist_ok=True) + # 重命名文件 + Path(source).rename(dest) + handler.namer = namer + handler.rotator = rotator return handler - def _install_excepthook(self) -> None: - def excepthook(exc_type, exc_value, exc_tb): - if issubclass(exc_type, KeyboardInterrupt): - return - self._logger.error("未捕获的异常", exc_info=(exc_type, exc_value, exc_tb)) - - sys.excepthook = excepthook - def configure(self) -> logging.Logger: if self._configured: return self._logger @@ -70,9 +75,6 @@ def configure(self) -> logging.Logger: console.setFormatter(formatter) self._logger.addHandler(console) - # 全局异常钩子 - self._install_excepthook() - self._configured = True return self._logger diff --git a/backend/app/scripts/data/system_menu.json b/backend/app/scripts/data/system_menu.json index a439ed90..d769656a 100644 --- a/backend/app/scripts/data/system_menu.json +++ b/backend/app/scripts/data/system_menu.json @@ -1939,25 +1939,6 @@ } ] }, - { - "name": "表单构建", - "type": 2, - "icon": "el-icon-Wallet", - "order": 2, - "permission": "gencode:gencode:query", - "route_name": "webcode", - "route_path": "/gencode/webcode", - "component_path": "gencode/webcode/index", - "status": true, - "keep_alive": true, - "hidden": false, - "always_show": false, - "title": "表单构建", - "params": null, - "affix": false, - "redirect": null, - "description": "表单构建" - }, { "name": "示例管理", "type": 2, diff --git a/backend/app/utils/gen_util.py b/backend/app/utils/gen_util.py index 136e9e81..7e990303 100644 --- a/backend/app/utils/gen_util.py +++ b/backend/app/utils/gen_util.py @@ -6,7 +6,7 @@ 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 GenTableSchema, GenTableColumnSchema +from app.api.v1.module_generator.gencode.schema import GenTableOutSchema, GenTableSchema, GenTableColumnSchema class GenUtils: @@ -24,46 +24,47 @@ def init_table(cls, gen_table: GenTableSchema) -> None: - None """ # 只有当字段为None时才设置默认值 - if gen_table.class_name is None: - gen_table.class_name = cls.convert_class_name(gen_table.table_name or "") - if gen_table.package_name is None: - gen_table.package_name = settings.package_name - if gen_table.module_name is None: - gen_table.module_name = settings.package_name.split('.')[-1] - if gen_table.business_name is None: - gen_table.business_name = gen_table.table_name.split('_')[-1] - if gen_table.function_name is None: - gen_table.function_name = re.sub(r'(?:表|测试)', '', gen_table.table_comment or "") + 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.business_name = gen_table.table_name.split('_')[-1] + gen_table.function_name = re.sub(r'(?:表|测试)', '', gen_table.table_comment or "") @classmethod - def init_column_field(cls, column: GenTableColumnSchema, table: GenTableSchema) -> None: + def init_column_field(cls, column: GenTableColumnSchema, table: GenTableOutSchema) -> None: """ 初始化列属性字段 参数: - column (GenTableColumnSchema): 业务表字段对象。 - - table (GenTableSchema): 业务表对象。 + - table (GenTableOutSchema): 业务表对象。 返回: - None """ data_type = cls.get_db_type(column.column_type or "") column_name = column.column_name or "" - # 只有当table_id为None时才设置 - if column.table_id is None: - column.table_id = table.table_id - # 只有当python_field为None时才设置 - if column.python_field is None: - column.python_field = column_name + column.table_id = table.id + column.python_field = cls.to_camel_case(column_name) # 只有当python_type为None时才设置默认类型 - if column.python_type is None: - # 根据数据库类型映射到Python类型(统一使用大写键以兼容MySQL) - column.python_type = GenConstant.DB_TO_PYTHON.get(data_type.upper(), "Any") + column.python_type = StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_PYTHON, data_type) # 查询类型:优先根据字段语义(如以name结尾走LIKE),否则默认EQ - if column.query_type is None: - column.query_type = GenConstant.QUERY_LIKE if column_name.lower().endswith("name") else GenConstant.QUERY_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' + + # 确保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' - # HTML类型:根据数据库类型设置基础控件,再根据字段语义进行覆写 if column.html_type is None: if cls.arrays_contains(GenConstant.COLUMNTYPE_STR, data_type) or cls.arrays_contains( GenConstant.COLUMNTYPE_TEXT, data_type @@ -96,37 +97,50 @@ def init_column_field(cls, column: GenTableColumnSchema, table: GenTableSchema) # 只有当is_insert为None时才设置插入字段(默认所有字段都需要插入) if column.is_insert is None: column.is_insert = GenConstant.REQUIRE - + else: + # 确保is_insert为字符串格式 + column.is_insert = str(column.is_insert) if column.is_insert is not None else '0' + # 只有当is_edit为None时才设置编辑字段 - if column.is_edit is None and not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_EDIT, column_name) and not (column.is_pk is not None and column.is_pk == GenConstant.REQUIRE): - column.is_edit = GenConstant.REQUIRE + if column.is_edit is None: + if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_EDIT, column_name) and column.is_pk != '1': + column.is_edit = GenConstant.REQUIRE + else: + column.is_edit = '0' + else: + # 确保is_edit为字符串格式 + column.is_edit = str(column.is_edit) if column.is_edit is not None else '0' + # 只有当is_list为None时才设置列表字段 - if column.is_list is None and not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_LIST, column_name) and not (column.is_pk is not None and column.is_pk == GenConstant.REQUIRE): - column.is_list = GenConstant.REQUIRE + if column.is_list is None: + if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_LIST, column_name) and column.is_pk != '1': + column.is_list = GenConstant.REQUIRE + else: + column.is_list = '0' + else: + # 确保is_list为字符串格式 + column.is_list = str(column.is_list) if column.is_list is not None else '0' + # 只有当is_query为None时才设置查询字段 - if column.is_query is None and not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_QUERY, column_name) and not (column.is_pk is not None and column.is_pk == GenConstant.REQUIRE): - column.is_query = GenConstant.REQUIRE - - # 只有当query_type为None时才设置查询字段类型 - if column.query_type is None: - if column_name.lower().endswith('name'): - column.query_type = GenConstant.QUERY_LIKE + if column.is_query is None: + if not cls.arrays_contains(GenConstant.COLUMNNAME_NOT_QUERY, column_name) and column.is_pk != '1': + column.is_query = GenConstant.REQUIRE + else: + column.is_query = '0' + else: + # 确保is_query为字符串格式 + column.is_query = str(column.is_query) if column.is_query is not None else '0' @classmethod def arrays_contains(cls, arr: List[str], target_value: str) -> bool: """ - 校验数组是否包含指定值(忽略大小写) - - 参数: - - arr (List[str]): 数组。 - - target_value (str): 需要校验的值。 + 校验数组是否包含指定值 - 返回: - - bool: 校验结果。 + param arr: 数组 + param target_value: 需要校验的值 + :return: 校验结果 """ - if target_value is None: - return False - return any(item.lower() == target_value.lower() for item in arr) + return target_value in arr @classmethod def convert_class_name(cls, table_name: str) -> str: @@ -189,13 +203,9 @@ def get_column_length(cls, column_type: str) -> int: 返回: - int: 字段长度(优先取第一个长度值,无法解析时返回0)。 """ - if '(' in column_type and ')' in column_type: - try: - inner = column_type.split('(')[1].split(')')[0] - first = inner.split(',')[0].strip() - return int(first) - except Exception: - return 0 + if '(' in column_type: + length = len(column_type.split('(')[1].split(')')[0]) + return length return 0 @classmethod @@ -211,4 +221,15 @@ def split_column_type(cls, column_type: str) -> List[str]: """ if '(' in column_type and ')' in column_type: return column_type.split('(')[1].split(')')[0].split(',') - return [] \ No newline at end of file + return [] + + @classmethod + def to_camel_case(cls, text: str) -> str: + """ + 将字符串转换为驼峰命名 + + param text: 需要转换的字符串 + :return: 驼峰命名 + """ + parts = text.split('_') + return parts[0] + ''.join(word.capitalize() for word in parts[1:]) \ No newline at end of file diff --git a/backend/app/utils/jinja2_template_util.py b/backend/app/utils/jinja2_template_util.py index 515906da..40e028b5 100644 --- a/backend/app/utils/jinja2_template_util.py +++ b/backend/app/utils/jinja2_template_util.py @@ -39,28 +39,31 @@ def get_env(cls): 返回: - Environment: Jinja2 环境对象。 """ - if cls._env is None: - # 确保模板目录存在 - template_dir = settings.TEMPLATE_DIR - if not os.path.exists(template_dir): - raise RuntimeError(f'模板目录不存在: {template_dir}') + try: + if cls._env is None: + # 确保模板目录存在 + template_dir = settings.TEMPLATE_DIR + if not os.path.exists(template_dir): + raise RuntimeError(f'模板目录不存在: {template_dir}') - cls._env = Environment( - loader=FileSystemLoader(settings.TEMPLATE_DIR), - autoescape=select_autoescape(['html', 'xml', 'jinja']), # 自动转义HTML - trim_blocks=True, # 删除多余的空行 - lstrip_blocks=True, # 删除行首空格 - keep_trailing_newline=True, # 保留行尾换行符 - enable_async=True, # 开启异步支持 - ) - cls._env.filters.update( - { - 'camel_to_snake': SnakeCaseUtil.camel_to_snake, - 'snake_to_camel': CamelCaseUtil.snake_to_camel, - 'get_sqlalchemy_type': cls.get_sqlalchemy_type, - } - ) - return cls._env + cls._env = Environment( + loader=FileSystemLoader(settings.TEMPLATE_DIR), + autoescape=select_autoescape(['html', 'xml', 'jinja', 'j2']), # 自动转义HTML + trim_blocks=True, # 删除多余的空行 + lstrip_blocks=True, # 删除行首空格 + keep_trailing_newline=True, # 保留行尾换行符 + enable_async=True, # 开启异步支持 + ) + cls._env.filters.update( + { + 'camel_to_snake': SnakeCaseUtil.camel_to_snake, + 'snake_to_camel': CamelCaseUtil.snake_to_camel, + 'get_sqlalchemy_type': cls.get_sqlalchemy_type, + } + ) + return cls._env + except Exception as e: + raise RuntimeError(f'初始化Jinja2模板引擎失败: {e}') @classmethod def get_template(cls, template_path: str) -> Template: @@ -90,36 +93,27 @@ def prepare_context(cls, gen_table: GenTableOutSchema) -> dict[str, Any]: - Dict[str, Any]: 模板上下文字典。 """ # 处理options为None的情况 - options = gen_table.options or '{}' - try: - params_obj = json.loads(options) - except json.JSONDecodeError: - params_obj = {} - + # if not gen_table.options: + # raise ValueError('请先完善生成配置信息') class_name = gen_table.class_name or '' module_name = gen_table.module_name or '' business_name = gen_table.business_name or '' package_name = gen_table.package_name or '' function_name = gen_table.function_name or '' - # 确保pk_column不为None - pk_column = gen_table.pk_column - if pk_column is None and gen_table.columns: - # 如果没有明确的主键列,使用第一个列作为主键 - pk_column = gen_table.columns[0] - context = { 'table_name': gen_table.table_name or '', + 'table_comment': gen_table.table_comment or '', 'function_name': function_name if StringUtil.is_not_empty(function_name) else '【请填写功能名称】', 'class_name': class_name, 'module_name': module_name, - 'business_name': business_name.capitalize() if business_name else '', - 'base_package': cls.get_package_prefix(package_name) if package_name else '', + 'business_name': business_name.capitalize(), + 'base_package': cls.get_package_prefix(package_name), 'package_name': package_name, 'datetime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), - 'pk_column': pk_column, - 'do_import_list': cls.get_import_list(gen_table, "model"), - 'vo_import_list': cls.get_import_list(gen_table, "schema"), + 'pk_column': gen_table.pk_column, + 'model_import_list': cls.get_model_import_list(gen_table), + 'schema_import_list': cls.get_schema_import_list(gen_table), 'permission_prefix': cls.get_permission_prefix(module_name, business_name), 'columns': gen_table.columns or [], 'table': gen_table, @@ -127,12 +121,10 @@ def prepare_context(cls, gen_table: GenTableOutSchema) -> dict[str, Any]: 'db_type': settings.DATABASE_TYPE, 'column_not_add_show': GenConstant.COLUMNNAME_NOT_ADD_SHOW, 'column_not_edit_show': GenConstant.COLUMNNAME_NOT_EDIT_SHOW, - 'primary_key': pk_column.python_field if pk_column else '' } return context - @classmethod def get_template_list(cls): """ @@ -156,6 +148,7 @@ def get_template_list(cls): ] return templates + @classmethod def get_file_name(cls, template: str, gen_table: GenTableOutSchema): """ @@ -215,42 +208,60 @@ def get_package_prefix(cls, package_name: str) -> str: return package_name[: package_name.rfind('.')] if '.' in package_name else package_name @classmethod - def get_import_list(cls, gen_table: GenTableOutSchema, model_type: str): + def get_schema_import_list(cls, gen_table: GenTableOutSchema): """ - 获取导入包列表。 + 获取schema模板导入包列表 - 参数: - - gen_table (GenTableOutSchema): 生成表的配置信息。 - - model_type (str): 模型类型 ("model" 或 "schema") - - 返回: - - List[str]: 导入包列表。 + :param gen_table: 生成表的配置信息 + :return: 导入包列表 """ columns = gen_table.columns or [] import_list = set() - - if model_type == "model": - import_list.add('from sqlalchemy import Column') - for column in columns: - column_type = column.column_type or '' - if model_type == "schema": - # Schema特定导入逻辑 - if column_type in GenConstant.TYPE_DATE: - import_list.add(f'from datetime import {column_type}') - elif column_type == GenConstant.TYPE_DECIMAL: - import_list.add('from decimal import Decimal') - else: # model - # Model特定导入逻辑 - data_type = cls.get_db_type(column_type) + if column.python_type in GenConstant.TYPE_DATE: + import_list.add(f'from datetime import {column.python_type}') + elif column.python_type == GenConstant.TYPE_DECIMAL: + import_list.add('from decimal import Decimal') + if gen_table.sub: + if gen_table.sub_table and gen_table.sub_table.columns: + sub_columns = gen_table.sub_table.columns or [] + for sub_column in sub_columns: + if sub_column.python_type in GenConstant.TYPE_DATE: + import_list.add(f'from datetime import {sub_column.python_type}') + elif sub_column.python_type == GenConstant.TYPE_DECIMAL: + import_list.add('from decimal import Decimal') + return cls.merge_same_imports(list(import_list), 'from datetime import') + + @classmethod + def get_model_import_list(cls, gen_table: GenTableOutSchema): + """ + 获取do模板导入包列表 + + :param gen_table: 生成表的配置信息 + :return: 导入包列表 + """ + 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) if data_type in GenConstant.COLUMNTYPE_GEOMETRY: import_list.add('from geoalchemy2 import Geometry') - sqlalchemy_type = GenConstant.DB_TO_SQLALCHEMY.get(data_type) - if sqlalchemy_type: - import_list.add(f'from sqlalchemy import {sqlalchemy_type}') - - return cls.merge_same_imports(list(import_list), - 'from datetime import' if model_type == "schema" else 'from sqlalchemy import') + import_list.add( + f'from sqlalchemy import {StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY, data_type)}' + ) + if gen_table.sub: + import_list.add('from sqlalchemy import ForeignKey') + if gen_table.sub_table and gen_table.sub_table.columns: + sub_columns = gen_table.sub_table.columns or [] + for sub_column in sub_columns: + if sub_column.column_type: + data_type = cls.get_db_type(sub_column.column_type) + import_list.add( + f'from sqlalchemy import {StringUtil.get_mapping_value_by_key_ignore_case(GenConstant.DB_TO_SQLALCHEMY, data_type)}' + ) + return cls.merge_same_imports(list(import_list), 'from sqlalchemy import') @classmethod def get_db_type(cls, column_type: str) -> str: @@ -367,10 +378,23 @@ def get_sqlalchemy_type(cls, column): 返回: - str: SQLAlchemy 类型字符串。 """ - column_type = getattr(column, 'column_type', str(column)) - if not column_type: - return "String" - - # 直接映射,简化逻辑 - base_type = column_type.split('(')[0] if '(' in column_type else column_type - return GenConstant.DB_TO_SQLALCHEMY.get(base_type, "String") \ No newline at end of file + if '(' in column: + column_type_list = column.split('(') + if column_type_list[0] in GenConstant.COLUMNTYPE_STR: + sqlalchemy_type = ( + StringUtil.get_mapping_value_by_key_ignore_case( + GenConstant.DB_TO_SQLALCHEMY, column_type_list[0] + ) + + '(' + + column_type_list[1] + ) + else: + sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case( + GenConstant.DB_TO_SQLALCHEMY, column_type_list[0] + ) + else: + sqlalchemy_type = StringUtil.get_mapping_value_by_key_ignore_case( + GenConstant.DB_TO_SQLALCHEMY, column + ) + + return sqlalchemy_type \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt index 564f8c8b..19f2e9e2 100755 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -22,7 +22,7 @@ pydantic-settings==2.5.2 # 配置设置 psutil==6.1.0 # 系统信息 python-multipart==0.0.9 # request.form() 对表单进行「解析」时安装 greenlet==3.1.1 # 协程框架 -bcrypt==4.3.0 # 密码加密解析 +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+的版本 @@ -35,5 +35,5 @@ PyMySQL==1.1.2 # mysql 异步操作数据库基于 pymysql:aiomys cryptography==45.0.2 # mysql8 密码加密 openai==1.55.2 # ai 大模型 oss2==2.18.4 # 阿里云对象存储 -rich==13.9.4 +rich==13.9.4 # 终端打印美化 sqlglot[rs]==27.8.0 # sql 解析 \ No newline at end of file diff --git a/frontend/src/api/generator/gencode.ts b/frontend/src/api/generator/gencode.ts index d1e3fa0f..9ecbb07d 100644 --- a/frontend/src/api/generator/gencode.ts +++ b/frontend/src/api/generator/gencode.ts @@ -153,6 +153,10 @@ export interface GenTableOutVO { table_name?: string; /** 表描述 */ table_comment?: string; + /** 关联子表的表名 */ + sub_table_name?: string; + /** 关联子表的外键名 */ + sub_table_fk_name?: string; /** 实体类名称 */ class_name?: string; /** 生成包路径 */ @@ -166,23 +170,7 @@ export interface GenTableOutVO { /** 生成代码方式(0zip压缩包 1自定义路径) */ gen_type?: string; /** 其它生成选项 */ - options?: string; - /** 上级菜单ID字段 */ - parent_menu_id?: number; - /** 上级菜单名称字段 */ - parent_menu_name?: string; - /** 是否为子表 */ - sub?: boolean; - /** 是否为树表 */ - tree?: boolean; - /** 是否为单表 */ - crud?: boolean; - /** 表描述 */ - description?: string; - /** 列列表 */ - columns?: GenTableColumnOutSchema[]; - /** 参数选项 */ - params?: GenTableOptionModel; + options?: GenTableOptionModel; } /** 表选项模型 */ @@ -193,12 +181,20 @@ export interface GenTableOptionModel { /** 代码生成业务表模型 */ export interface GenTableSchema extends GenTableOutVO { + /** 表描述 */ + description?: string; + /** 上级菜单ID字段 */ + parent_menu_id?: number; + /** 上级菜单名称字段 */ + parent_menu_name?: string; /** 主键信息 */ pk_column?: GenTableColumnOutSchema; /** 子表信息 */ sub_table?: GenTableSchema; /** 表列信息 */ columns: GenTableColumnOutSchema[]; + /** 是否为子表 */ + sub?: boolean; } /** 代码生成业务表列模型 */ @@ -325,5 +321,5 @@ export interface BasicInfoFormData { table_name?: string; table_comment?: string; class_name?: string; - remark?: string; + description?: string; } diff --git a/frontend/src/utils/generator/config.ts b/frontend/src/utils/generator/config.ts deleted file mode 100644 index e3cde828..00000000 --- a/frontend/src/utils/generator/config.ts +++ /dev/null @@ -1,600 +0,0 @@ -// TypeScript interfaces for form configuration -export interface FormConf { - formRef: string; - formModel: string; - size: string; - labelPosition: string; - labelWidth: number; - formRules: string; - gutter: number; - disabled: boolean; - span: number; - formBtns: boolean; -} - -export interface BaseComponent { - label: string; - tag: string; - tagIcon: string; - defaultValue?: any; - span?: number; - labelWidth?: number | null; - style?: Record; - disabled?: boolean; - required?: boolean; - regList?: Array<{ - pattern: string; - message: string; - }>; - changeTag?: boolean; - document?: string; -} - -export interface ComponentOption { - label: string; - value: any; - disabled?: boolean; - id?: number; - children?: ComponentOption[]; -} - -export interface FormComponent extends BaseComponent { - // Input related - type?: string; - placeholder?: string; - clearable?: boolean; - prepend?: string; - append?: string; - 'prefix-icon'?: string; - 'suffix-icon'?: string; - maxlength?: number | null; - 'show-word-limit'?: boolean; - readonly?: boolean; - autosize?: { - minRows: number; - maxRows: number; - }; - 'show-password'?: boolean; - - // Number input related - min?: number; - max?: number; - step?: number; - 'step-strictly'?: boolean; - precision?: number; - 'controls-position'?: string; - - // Select related - filterable?: boolean; - multiple?: boolean; - options?: ComponentOption[]; - - // Cascader related - props?: Record; - 'show-all-levels'?: boolean; - dataType?: string; - labelKey?: string; - valueKey?: string; - childrenKey?: string; - separator?: string; - - // Radio/Checkbox related - optionType?: string; - border?: boolean; - size?: string; - - // Switch related - 'active-text'?: string; - 'inactive-text'?: string; - 'active-color'?: string | null; - 'inactive-color'?: string | null; - 'active-value'?: any; - 'inactive-value'?: any; - - // Slider related - 'show-stops'?: boolean; - range?: boolean; - - // Date/Time picker related - format?: string; - 'value-format'?: string; - 'is-range'?: boolean; - 'range-separator'?: string; - 'start-placeholder'?: string; - 'end-placeholder'?: string; - 'picker-options'?: any; - - // Rate related - 'allow-half'?: boolean; - 'show-text'?: boolean; - 'show-score'?: boolean; - - // Color picker related - 'color-format'?: string; - 'show-alpha'?: boolean; - - // Upload related - action?: string; - accept?: string; - name?: string; - 'auto-upload'?: boolean; - showTip?: boolean; - buttonText?: string; - fileSize?: number; - sizeUnit?: string; - 'list-type'?: string; - tip?: string; -} - -export interface LayoutComponent { - layout: string; - tagIcon: string; - type?: string; - justify?: string; - align?: string; - label: string; - layoutTree?: boolean; - children?: any[]; - tag?: string; - default?: string; - icon?: string; - size?: string; - labelWidth?: number | null; - changeTag?: boolean; - span?: number; - disabled?: boolean; - document?: string; -} - -export const formConf: FormConf = { - formRef: 'formRef', - formModel: 'formData', - size: 'default', - labelPosition: 'right', - labelWidth: 100, - formRules: 'rules', - gutter: 15, - disabled: false, - span: 24, - formBtns: true, -} - -export const inputComponents: FormComponent[] = [ - { - label: '单行文本', - tag: 'el-input', - tagIcon: 'input', - type: 'text', - placeholder: '请输入', - defaultValue: undefined, - span: 24, - labelWidth: null, - style: { width: '100%' }, - clearable: true, - prepend: '', - append: '', - 'prefix-icon': '', - 'suffix-icon': '', - maxlength: null, - 'show-word-limit': false, - readonly: false, - disabled: false, - required: true, - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/input', - }, - { - label: '多行文本', - tag: 'el-input', - tagIcon: 'textarea', - type: 'textarea', - placeholder: '请输入', - defaultValue: undefined, - span: 24, - labelWidth: null, - autosize: { - minRows: 4, - maxRows: 4, - }, - style: { width: '100%' }, - maxlength: null, - 'show-word-limit': false, - readonly: false, - disabled: false, - required: true, - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/input', - }, - { - label: '密码', - tag: 'el-input', - tagIcon: 'password', - type: 'password', - placeholder: '请输入', - defaultValue: undefined, - span: 24, - 'show-password': true, - labelWidth: null, - style: { width: '100%' }, - clearable: true, - prepend: '', - append: '', - 'prefix-icon': '', - 'suffix-icon': '', - maxlength: null, - 'show-word-limit': false, - readonly: false, - disabled: false, - required: true, - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/input', - }, - { - label: '计数器', - tag: 'el-input-number', - tagIcon: 'number', - placeholder: '', - defaultValue: undefined, - span: 24, - labelWidth: null, - min: undefined, - max: undefined, - step: undefined, - 'step-strictly': false, - precision: undefined, - 'controls-position': '', - disabled: false, - required: true, - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/input-number', - }, -] - -export const selectComponents: FormComponent[] = [ - { - label: '下拉选择', - tag: 'el-select', - tagIcon: 'select', - placeholder: '请选择', - defaultValue: undefined, - span: 24, - labelWidth: null, - style: { width: '100%' }, - clearable: true, - disabled: false, - required: true, - filterable: false, - multiple: false, - options: [ - { - label: '选项一', - value: 1, - }, - { - label: '选项二', - value: 2, - }, - ], - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/select', - }, - { - label: '级联选择', - tag: 'el-cascader', - tagIcon: 'cascader', - placeholder: '请选择', - defaultValue: [], - span: 24, - labelWidth: null, - style: { width: '100%' }, - props: { - props: { - multiple: false, - }, - }, - 'show-all-levels': true, - disabled: false, - clearable: true, - filterable: false, - required: true, - options: [ - { - id: 1, - value: 1, - label: '选项1', - children: [ - { - id: 2, - value: 2, - label: '选项1-1', - }, - ], - }, - ], - dataType: 'dynamic', - labelKey: 'label', - valueKey: 'value', - childrenKey: 'children', - separator: '/', - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/cascader', - }, - { - label: '单选框组', - tag: 'el-radio-group', - tagIcon: 'radio', - defaultValue: 0, - span: 24, - labelWidth: null, - style: {}, - optionType: 'default', - border: false, - size: 'default', - disabled: false, - required: true, - options: [ - { - label: '选项一', - value: 1, - }, - { - label: '选项二', - value: 2, - }, - ], - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/radio', - }, - { - label: '多选框组', - tag: 'el-checkbox-group', - tagIcon: 'checkbox', - defaultValue: [], - span: 24, - labelWidth: null, - style: {}, - optionType: 'default', - border: false, - size: 'default', - disabled: false, - required: true, - options: [ - { - label: '选项一', - value: 1, - }, - { - label: '选项二', - value: 2, - }, - ], - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/checkbox', - }, - { - label: '开关', - tag: 'el-switch', - tagIcon: 'switch', - defaultValue: false, - span: 24, - labelWidth: null, - style: {}, - disabled: false, - required: true, - 'active-text': '', - 'inactive-text': '', - 'active-color': null, - 'inactive-color': null, - 'active-value': true, - 'inactive-value': false, - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/switch', - }, - { - label: '滑块', - tag: 'el-slider', - tagIcon: 'slider', - defaultValue: null, - span: 24, - labelWidth: null, - disabled: false, - required: true, - min: 0, - max: 100, - step: 1, - 'show-stops': false, - range: false, - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/slider', - }, - { - label: '时间选择', - tag: 'el-time-picker', - tagIcon: 'time', - placeholder: '请选择', - defaultValue: '', - span: 24, - labelWidth: null, - style: { width: '100%' }, - disabled: false, - clearable: true, - required: true, - format: 'HH:mm:ss', - 'value-format': 'HH:mm:ss', - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/time-picker', - }, - { - label: '时间范围', - tag: 'el-time-picker', - tagIcon: 'time-range', - defaultValue: null, - span: 24, - labelWidth: null, - style: { width: '100%' }, - disabled: false, - clearable: true, - required: true, - 'is-range': true, - 'range-separator': '至', - 'start-placeholder': '开始时间', - 'end-placeholder': '结束时间', - format: 'HH:mm:ss', - 'value-format': 'HH:mm:ss', - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/time-picker', - }, - { - label: '日期选择', - tag: 'el-date-picker', - tagIcon: 'date', - placeholder: '请选择', - defaultValue: null, - type: 'date', - span: 24, - labelWidth: null, - style: { width: '100%' }, - disabled: false, - clearable: true, - required: true, - format: 'YYYY-MM-DD', - 'value-format': 'YYYY-MM-DD', - readonly: false, - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/date-picker', - }, - { - label: '日期范围', - tag: 'el-date-picker', - tagIcon: 'date-range', - defaultValue: null, - span: 24, - labelWidth: null, - style: { width: '100%' }, - type: 'daterange', - 'range-separator': '至', - 'start-placeholder': '开始日期', - 'end-placeholder': '结束日期', - disabled: false, - clearable: true, - required: true, - format: 'YYYY-MM-DD', - 'value-format': 'YYYY-MM-DD', - readonly: false, - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/date-picker', - }, - { - label: '评分', - tag: 'el-rate', - tagIcon: 'rate', - defaultValue: 0, - span: 24, - labelWidth: null, - style: {}, - max: 5, - 'allow-half': false, - 'show-text': false, - 'show-score': false, - disabled: false, - required: true, - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/rate', - }, - { - label: '颜色选择', - tag: 'el-color-picker', - tagIcon: 'color', - defaultValue: null, - labelWidth: null, - 'show-alpha': false, - 'color-format': '', - disabled: false, - required: true, - size: 'default', - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/color-picker', - }, - { - label: '上传', - tag: 'el-upload', - tagIcon: 'upload', - action: 'https://jsonplaceholder.typicode.com/posts/', - defaultValue: null, - labelWidth: null, - disabled: false, - required: true, - accept: '', - name: 'file', - 'auto-upload': true, - showTip: false, - buttonText: '点击上传', - fileSize: 2, - sizeUnit: 'MB', - 'list-type': 'text', - multiple: false, - regList: [], - changeTag: true, - document: 'https://element-plus.org/zh-CN/component/upload', - tip: '只能上传不超过 2MB 的文件', - style: { width: '100%' }, - }, -] - -export const layoutComponents: LayoutComponent[] = [ - { - layout: 'rowFormItem', - tagIcon: 'row', - type: 'default', - justify: 'start', - align: 'top', - label: '行容器', - layoutTree: true, - children: [], - document: 'https://element-plus.org/zh-CN/component/layout', - }, - { - layout: 'colFormItem', - label: '按钮', - changeTag: true, - labelWidth: null, - tag: 'el-button', - tagIcon: 'button', - span: 24, - default: '主要按钮', - type: 'primary', - icon: 'Search', - size: 'default', - disabled: false, - document: 'https://element-plus.org/zh-CN/component/button', - }, -] - -// 组件rule的触发方式,无触发方式的组件不生成rule -export const trigger: Record = { - 'el-input': 'blur', - 'el-input-number': 'blur', - 'el-select': 'change', - 'el-radio-group': 'change', - 'el-checkbox-group': 'change', - 'el-cascader': 'change', - 'el-time-picker': 'change', - 'el-date-picker': 'change', - 'el-rate': 'change', -} diff --git a/frontend/src/utils/generator/css.ts b/frontend/src/utils/generator/css.ts deleted file mode 100644 index e62360b3..00000000 --- a/frontend/src/utils/generator/css.ts +++ /dev/null @@ -1,27 +0,0 @@ -interface FormConfig { - fields: FormElement[] -} - -interface FormElement { - tag: string - children?: FormElement[] -} - -const styles: Record = { - 'el-rate': '.el-rate{display: inline-block; vertical-align: text-top;}', - 'el-upload': '.el-upload__tip{line-height: 1.2;}' -} - -function addCss(cssList: string[], el: FormElement): void { - const css = styles[el.tag] - css && cssList.indexOf(css) === -1 && cssList.push(css) - if (el.children) { - el.children.forEach(el2 => addCss(cssList, el2)) - } -} - -export function makeUpCss(conf: FormConfig): string { - const cssList: string[] = [] - conf.fields.forEach(el => addCss(cssList, el)) - return cssList.join('\n') -} diff --git a/frontend/src/utils/generator/drawingDefalut.ts b/frontend/src/utils/generator/drawingDefalut.ts deleted file mode 100644 index 038a5c13..00000000 --- a/frontend/src/utils/generator/drawingDefalut.ts +++ /dev/null @@ -1,59 +0,0 @@ -export interface DrawingItem { - layout: string; - tagIcon: string; - label: string; - vModel: string; - formId: number; - tag: string; - placeholder: string; - defaultValue: string; - span: number; - style: Record; - clearable: boolean; - prepend: string; - append: string; - 'prefix-icon': string; - 'suffix-icon': string; - maxlength: number; - 'show-word-limit': boolean; - readonly: boolean; - disabled: boolean; - required: boolean; - changeTag: boolean; - regList: Array<{ - pattern: string; - message: string; - }>; -} - -const drawingDefault: DrawingItem[] = [ - { - layout: 'colFormItem', - tagIcon: 'input', - label: '手机号', - vModel: 'mobile', - formId: 6, - tag: 'el-input', - placeholder: '请输入手机号', - defaultValue: '', - span: 24, - style: { width: '100%' }, - clearable: true, - prepend: '', - append: '', - 'prefix-icon': 'Cellphone', - 'suffix-icon': '', - maxlength: 11, - 'show-word-limit': true, - readonly: false, - disabled: false, - required: true, - changeTag: true, - regList: [{ - pattern: '/^1(3|4|5|7|8|9)\\d{9}$/', - message: '手机号格式错误' - }] - } -] - -export default drawingDefault; diff --git a/frontend/src/utils/generator/html.ts b/frontend/src/utils/generator/html.ts deleted file mode 100644 index 40c0cd85..00000000 --- a/frontend/src/utils/generator/html.ts +++ /dev/null @@ -1,455 +0,0 @@ -/* eslint-disable max-len */ -import { trigger } from './config' - -interface FormConfig { - formRef: string - formModel: string - formRules: string - size: string - disabled?: boolean - labelWidth: number - labelPosition: string - gutter: number - formBtns: boolean - fields: FormElement[] -} - -interface FormElement { - tag: string - layout: string - label?: string - vModel?: string - span: number - labelWidth?: number | null - required?: boolean - disabled?: boolean - type?: string - placeholder?: string - maxlength?: number | null - 'show-word-limit'?: boolean - readonly?: boolean - clearable?: boolean - 'prefix-icon'?: string - 'suffix-icon'?: string - 'show-password'?: boolean - autosize?: { minRows: number; maxRows: number } - 'controls-position'?: string - min?: number | undefined - max?: number | undefined - step?: number | undefined - 'step-strictly'?: boolean - precision?: number | undefined - multiple?: boolean - filterable?: boolean - options?: Array<{ label: string; value: any; disabled?: boolean }> - size?: string - optionType?: string - border?: boolean - 'active-text'?: string - 'inactive-text'?: string - 'active-color'?: string | null - 'inactive-color'?: string | null - 'active-value'?: any - 'inactive-value'?: any - 'show-stops'?: boolean - range?: boolean - 'is-range'?: boolean - 'range-separator'?: string - 'start-placeholder'?: string - 'end-placeholder'?: string - format?: string - 'value-format'?: string - 'picker-options'?: any - 'allow-half'?: boolean - 'show-text'?: boolean - 'show-score'?: boolean - 'show-alpha'?: boolean - 'color-format'?: string - action?: string - 'list-type'?: string - accept?: string - name?: string - 'auto-upload'?: boolean - showTip?: boolean - buttonText?: string - fileSize?: number - sizeUnit?: string - 'show-all-levels'?: boolean - props?: any - separator?: string - icon?: string - default?: string - justify?: string - align?: string - gutter?: number - children?: FormElement[] - style?: Record - prepend?: string - append?: string -} - -let confGlobal: FormConfig -let someSpanIsNot24: boolean - -export function dialogWrapper(str: string): string { - return ` - ${str} - - ` -} - -export function vueTemplate(str: string): string { - return `` -} - -export function vueScript(str: string): string { - return `` -} - -export function cssStyle(cssStr: string): string { - return `` -} - -function buildFormTemplate(conf: FormConfig, child: string, type: string): string { - let labelPosition = '' - if (conf.labelPosition !== 'right') { - labelPosition = `label-position="${conf.labelPosition}"` - } - const disabled = conf.disabled ? `:disabled="${conf.disabled}"` : '' - let str = ` - ${child} - ${buildFromBtns(conf, type)} - ` - if (someSpanIsNot24) { - str = ` - ${str} - ` - } - return str -} - -function buildFromBtns(conf: FormConfig, type: string): string { - let str = '' - if (conf.formBtns && type === 'file') { - str = ` - 提交 - 重置 - ` - if (someSpanIsNot24) { - str = ` - ${str} - ` - } - } - return str -} - -// span不为24的用el-col包裹 -function colWrapper(element: FormElement, str: string): string { - if (someSpanIsNot24 || element.span !== 24) { - return ` - ${str} - ` - } - return str -} - -const layouts: Record string> = { - colFormItem(element: FormElement): string { - let labelWidth = '' - if (element.labelWidth && element.labelWidth !== confGlobal.labelWidth) { - labelWidth = `label-width="${element.labelWidth}px"` - } - const required = !trigger[element.tag as keyof typeof trigger] && element.required ? 'required' : '' - const tagDom = tags[element.tag as keyof typeof tags] ? tags[element.tag as keyof typeof tags](element) : null - let str = ` - ${tagDom} - ` - str = colWrapper(element, str) - return str - }, - rowFormItem(element: FormElement): string { - const type = element.type === 'default' ? '' : `type="${element.type}"` - const justify = element.type === 'default' ? '' : `justify="${element.justify}"` - const align = element.type === 'default' ? '' : `align="${element.align}"` - const gutter = element.gutter ? `gutter="${element.gutter}"` : '' - const children = element.children?.map(el => layouts[el.layout](el)) || [] - let str = ` - ${children.join('\n')} - ` - str = colWrapper(element, str) - return str - } -} - -const tags: Record string> = { - 'el-button': (el: FormElement): string => { - const { - tag, disabled - } = attrBuilder(el) - const type = el.type ? `type="${el.type}"` : '' - const icon = el.icon ? `icon="${el.icon}"` : '' - const size = el.size ? `size="${el.size}"` : '' - let child = buildElButtonChild(el) - - if (child) child = `\n${child}\n` // 换行 - return `<${el.tag} ${type} ${icon} ${size} ${disabled}>${child}` - }, - 'el-input': (el: FormElement): string => { - const { - disabled, vModel, clearable, placeholder, width - } = attrBuilder(el) - const maxlength = el.maxlength ? `:maxlength="${el.maxlength}"` : '' - const showWordLimit = el['show-word-limit'] ? 'show-word-limit' : '' - const readonly = el.readonly ? 'readonly' : '' - const prefixIcon = el['prefix-icon'] ? `prefix-icon='${el['prefix-icon']}'` : '' - const suffixIcon = el['suffix-icon'] ? `suffix-icon='${el['suffix-icon']}'` : '' - const showPassword = el['show-password'] ? 'show-password' : '' - const type = el.type ? `type="${el.type}"` : '' - const autosize = el.autosize && el.autosize.minRows - ? `:autosize="{minRows: ${el.autosize.minRows}, maxRows: ${el.autosize.maxRows}}"` - : '' - let child = buildElInputChild(el) - - if (child) child = `\n${child}\n` // 换行 - return `<${el.tag} ${vModel} ${type} ${placeholder} ${maxlength} ${showWordLimit} ${readonly} ${disabled} ${clearable} ${prefixIcon} ${suffixIcon} ${showPassword} ${autosize} ${width}>${child}` - }, - 'el-input-number': (el: FormElement): string => { - const { disabled, vModel, placeholder } = attrBuilder(el) - const controlsPosition = el['controls-position'] ? `controls-position=${el['controls-position']}` : '' - const min = el.min ? `:min='${el.min}'` : '' - const max = el.max ? `:max='${el.max}'` : '' - const step = el.step ? `:step='${el.step}'` : '' - const stepStrictly = el['step-strictly'] ? 'step-strictly' : '' - const precision = el.precision ? `:precision='${el.precision}'` : '' - - return `<${el.tag} ${vModel} ${placeholder} ${step} ${stepStrictly} ${precision} ${controlsPosition} ${min} ${max} ${disabled}>` - }, - 'el-select': (el: FormElement): string => { - const { - disabled, vModel, clearable, placeholder, width - } = attrBuilder(el) - const filterable = el.filterable ? 'filterable' : '' - const multiple = el.multiple ? 'multiple' : '' - let child = buildElSelectChild(el) - - if (child) child = `\n${child}\n` // 换行 - return `<${el.tag} ${vModel} ${placeholder} ${disabled} ${multiple} ${filterable} ${clearable} ${width}>${child}` - }, - 'el-radio-group': (el: FormElement): string => { - const { disabled, vModel } = attrBuilder(el) - const size = `size="${el.size}"` - let child = buildElRadioGroupChild(el) - - if (child) child = `\n${child}\n` // 换行 - return `<${el.tag} ${vModel} ${size} ${disabled}>${child}` - }, - 'el-checkbox-group': (el: FormElement): string => { - const { disabled, vModel } = attrBuilder(el) - const size = `size="${el.size}"` - const min = el.min ? `:min="${el.min}"` : '' - const max = el.max ? `:max="${el.max}"` : '' - let child = buildElCheckboxGroupChild(el) - - if (child) child = `\n${child}\n` // 换行 - return `<${el.tag} ${vModel} ${min} ${max} ${size} ${disabled}>${child}` - }, - 'el-switch': (el: FormElement): string => { - const { disabled, vModel } = attrBuilder(el) - const activeText = el['active-text'] ? `active-text="${el['active-text']}"` : '' - const inactiveText = el['inactive-text'] ? `inactive-text="${el['inactive-text']}"` : '' - const activeColor = el['active-color'] ? `active-color="${el['active-color']}"` : '' - const inactiveColor = el['inactive-color'] ? `inactive-color="${el['inactive-color']}"` : '' - const activeValue = el['active-value'] !== true ? `:active-value='${JSON.stringify(el['active-value'])}'` : '' - const inactiveValue = el['inactive-value'] !== false ? `:inactive-value='${JSON.stringify(el['inactive-value'])}'` : '' - - return `<${el.tag} ${vModel} ${activeText} ${inactiveText} ${activeColor} ${inactiveColor} ${activeValue} ${inactiveValue} ${disabled}>` - }, - 'el-cascader': (el: FormElement): string => { - const { - disabled, vModel, clearable, placeholder, width - } = attrBuilder(el) - const options = el.options ? `:options="${el.vModel}Options"` : '' - const props = el.props ? `:props="${el.vModel}Props"` : '' - const showAllLevels = el['show-all-levels'] ? '' : ':show-all-levels="false"' - const filterable = el.filterable ? 'filterable' : '' - const separator = el.separator === '/' ? '' : `separator="${el.separator}"` - - return `<${el.tag} ${vModel} ${options} ${props} ${width} ${showAllLevels} ${placeholder} ${separator} ${filterable} ${clearable} ${disabled}>` - }, - 'el-slider': (el: FormElement): string => { - const { disabled, vModel } = attrBuilder(el) - const min = el.min ? `:min='${el.min}'` : '' - const max = el.max ? `:max='${el.max}'` : '' - const step = el.step ? `:step='${el.step}'` : '' - const range = el.range ? 'range' : '' - const showStops = el['show-stops'] ? `:show-stops="${el['show-stops']}"` : '' - - return `<${el.tag} ${min} ${max} ${step} ${vModel} ${range} ${showStops} ${disabled}>` - }, - 'el-time-picker': (el: FormElement): string => { - const { - disabled, vModel, clearable, placeholder, width - } = attrBuilder(el) - const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : '' - const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : '' - const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : '' - const isRange = el['is-range'] ? 'is-range' : '' - const format = el.format ? `format="${el.format}"` : '' - const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : '' - const pickerOptions = el['picker-options'] ? `:picker-options='${JSON.stringify(el['picker-options'])}'` : '' - - return `<${el.tag} ${vModel} ${isRange} ${format} ${valueFormat} ${pickerOptions} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${disabled}>` - }, - 'el-date-picker': (el: FormElement): string => { - const { - disabled, vModel, clearable, placeholder, width - } = attrBuilder(el) - const startPlaceholder = el['start-placeholder'] ? `start-placeholder="${el['start-placeholder']}"` : '' - const endPlaceholder = el['end-placeholder'] ? `end-placeholder="${el['end-placeholder']}"` : '' - const rangeSeparator = el['range-separator'] ? `range-separator="${el['range-separator']}"` : '' - const format = el.format ? `format="${el.format}"` : '' - const valueFormat = el['value-format'] ? `value-format="${el['value-format']}"` : '' - const type = el.type === 'date' ? '' : `type="${el.type}"` - const readonly = el.readonly ? 'readonly' : '' - - return `<${el.tag} ${type} ${vModel} ${format} ${valueFormat} ${width} ${placeholder} ${startPlaceholder} ${endPlaceholder} ${rangeSeparator} ${clearable} ${readonly} ${disabled}>` - }, - 'el-rate': (el: FormElement): string => { - const { disabled, vModel } = attrBuilder(el) - const max = el.max ? `:max='${el.max}'` : '' - const allowHalf = el['allow-half'] ? 'allow-half' : '' - const showText = el['show-text'] ? 'show-text' : '' - const showScore = el['show-score'] ? 'show-score' : '' - - return `<${el.tag} ${vModel} ${allowHalf} ${showText} ${showScore} ${disabled}>` - }, - 'el-color-picker': (el: FormElement): string => { - const { disabled, vModel } = attrBuilder(el) - const size = `size="${el.size}"` - const showAlpha = el['show-alpha'] ? 'show-alpha' : '' - const colorFormat = el['color-format'] ? `color-format="${el['color-format']}"` : '' - - return `<${el.tag} ${vModel} ${size} ${showAlpha} ${colorFormat} ${disabled}>` - }, - 'el-upload': (el: FormElement): string => { - const disabled = el.disabled ? ':disabled=\'true\'' : '' - const action = el.action ? `:action="${el.vModel}Action"` : '' - const multiple = el.multiple ? 'multiple' : '' - const listType = el['list-type'] !== 'text' ? `list-type="${el['list-type']}"` : '' - const accept = el.accept ? `accept="${el.accept}"` : '' - const name = el.name !== 'file' ? `name="${el.name}"` : '' - const autoUpload = el['auto-upload'] === false ? ':auto-upload="false"' : '' - const beforeUpload = `:before-upload="${el.vModel}BeforeUpload"` - const fileList = `:file-list="${el.vModel}fileList"` - const ref = `ref="${el.vModel}"` - let child = buildElUploadChild(el) - - if (child) child = `\n${child}\n` // 换行 - return `<${el.tag} ${ref} ${fileList} ${action} ${autoUpload} ${multiple} ${beforeUpload} ${listType} ${accept} ${name} ${disabled}>${child}` - } -} - -interface AttrBuilderResult { - vModel: string - clearable: string - placeholder: string - width: string - disabled: string - tag?: string -} - -function attrBuilder(el: FormElement): AttrBuilderResult { - return { - vModel: `v-model="${confGlobal.formModel}.${el.vModel}"`, - clearable: el.clearable ? 'clearable' : '', - placeholder: el.placeholder ? `placeholder="${el.placeholder}"` : '', - width: el.style && el.style.width ? ':style="{width: \'100%\'}"' : '', - disabled: el.disabled ? ':disabled=\'true\'' : '' - } -} - -// el-buttin 子级 -function buildElButtonChild(conf: FormElement): string { - const children: string[] = [] - if (conf.default) { - children.push(conf.default) - } - return children.join('\n') -} - -// el-input innerHTML -function buildElInputChild(conf: FormElement): string { - const children: string[] = [] - if (conf.prepend) { - children.push(``) - } - if (conf.append) { - children.push(``) - } - return children.join('\n') -} - -function buildElSelectChild(conf: FormElement): string { - const children: string[] = [] - if (conf.options && conf.options.length) { - children.push(``) - } - return children.join('\n') -} - -function buildElRadioGroupChild(conf: FormElement): string { - const children: string[] = [] - if (conf.options && conf.options.length) { - const tag = conf.optionType === 'button' ? 'el-radio-button' : 'el-radio' - const border = conf.border ? 'border' : '' - children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :value="item.value" :disabled="item.disabled" ${border}>{{item.label}}`) - } - return children.join('\n') -} - -function buildElCheckboxGroupChild(conf: FormElement): string { - const children: string[] = [] - if (conf.options && conf.options.length) { - const tag = conf.optionType === 'button' ? 'el-checkbox-button' : 'el-checkbox' - const border = conf.border ? 'border' : '' - children.push(`<${tag} v-for="(item, index) in ${conf.vModel}Options" :key="index" :label="item.value" :value="item.label" :disabled="item.disabled" ${border} />`) - } - return children.join('\n') -} - -function buildElUploadChild(conf: FormElement): string { - const list: string[] = [] - if (conf['list-type'] === 'picture-card') list.push('') - else list.push(`${conf.buttonText}`) - if (conf.showTip) list.push(`
只能上传不超过 ${conf.fileSize}${conf.sizeUnit} 的${conf.accept}文件
`) - return list.join('\n') -} - -export function makeUpHtml(conf: FormConfig, type: string): string { - const htmlList: string[] = [] - confGlobal = conf - someSpanIsNot24 = conf.fields.some(item => item.span !== 24) - conf.fields.forEach(el => { - htmlList.push(layouts[el.layout](el)) - }) - const htmlStr = htmlList.join('\n') - - let temp = buildFormTemplate(conf, htmlStr, type) - if (type === 'dialog') { - temp = dialogWrapper(temp) - } - confGlobal = null as any - return temp -} diff --git a/frontend/src/utils/generator/icon.json b/frontend/src/utils/generator/icon.json deleted file mode 100644 index 2d9999a3..00000000 --- a/frontend/src/utils/generator/icon.json +++ /dev/null @@ -1 +0,0 @@ -["platform-eleme","eleme","delete-solid","delete","s-tools","setting","user-solid","user","phone","phone-outline","more","more-outline","star-on","star-off","s-goods","goods","warning","warning-outline","question","info","remove","circle-plus","success","error","zoom-in","zoom-out","remove-outline","circle-plus-outline","circle-check","circle-close","s-help","help","minus","plus","check","close","picture","picture-outline","picture-outline-round","upload","upload2","download","camera-solid","camera","video-camera-solid","video-camera","message-solid","bell","s-cooperation","s-order","s-platform","s-fold","s-unfold","s-operation","s-promotion","s-home","s-release","s-ticket","s-management","s-open","s-shop","s-marketing","s-flag","s-comment","s-finance","s-claim","s-custom","s-opportunity","s-data","s-check","s-grid","menu","share","d-caret","caret-left","caret-right","caret-bottom","caret-top","bottom-left","bottom-right","back","right","bottom","top","top-left","top-right","arrow-left","arrow-right","arrow-down","arrow-up","d-arrow-left","d-arrow-right","video-pause","video-play","refresh","refresh-right","refresh-left","finished","sort","sort-up","sort-down","rank","loading","view","c-scale-to-original","date","edit","edit-outline","folder","folder-opened","folder-add","folder-remove","folder-delete","folder-checked","tickets","document-remove","document-delete","document-copy","document-checked","document","document-add","printer","paperclip","takeaway-box","search","monitor","attract","mobile","scissors","umbrella","headset","brush","mouse","coordinate","magic-stick","reading","data-line","data-board","pie-chart","data-analysis","collection-tag","film","suitcase","suitcase-1","receiving","collection","files","notebook-1","notebook-2","toilet-paper","office-building","school","table-lamp","house","no-smoking","smoking","shopping-cart-full","shopping-cart-1","shopping-cart-2","shopping-bag-1","shopping-bag-2","sold-out","sell","present","box","bank-card","money","coin","wallet","discount","price-tag","news","guide","male","female","thumb","cpu","link","connection","open","turn-off","set-up","chat-round","chat-line-round","chat-square","chat-dot-round","chat-dot-square","chat-line-square","message","postcard","position","turn-off-microphone","microphone","close-notification","bangzhu","time","odometer","crop","aim","switch-button","full-screen","copy-document","mic","stopwatch","medal-1","medal","trophy","trophy-1","first-aid-kit","discover","place","location","location-outline","location-information","add-location","delete-location","map-location","alarm-clock","timer","watch-1","watch","lock","unlock","key","service","mobile-phone","bicycle","truck","ship","basketball","football","soccer","baseball","wind-power","light-rain","lightning","heavy-rain","sunrise","sunrise-1","sunset","sunny","cloudy","partly-cloudy","cloudy-and-sunny","moon","moon-night","dish","dish-1","food","chicken","fork-spoon","knife-fork","burger","tableware","sugar","dessert","ice-cream","hot-water","water-cup","coffee-cup","cold-drink","goblet","goblet-full","goblet-square","goblet-square-full","refrigerator","grape","watermelon","cherry","apple","pear","orange","coffee","ice-tea","ice-drink","milk-tea","potato-strips","lollipop","ice-cream-square","ice-cream-round"] \ No newline at end of file diff --git a/frontend/src/utils/generator/js.ts b/frontend/src/utils/generator/js.ts deleted file mode 100644 index aea74951..00000000 --- a/frontend/src/utils/generator/js.ts +++ /dev/null @@ -1,415 +0,0 @@ -import { titleCase } from '@/utils/index' -import { trigger } from './config' - -// 文件大小设置 -const units: Record = { - KB: '1024', - MB: '1024 / 1024', - GB: '1024 / 1024 / 1024', -} - -interface FormElement { - vModel?: string - defaultValue?: any - multiple?: boolean - regList?: Array<{ pattern: string; message: string }> - required?: boolean - placeholder?: string - label?: string - tag: string - options?: Array<{ label: string; value: any }> - dataType?: string - props?: { props: any } - action?: string - 'auto-upload'?: boolean - fileSize?: number - sizeUnit?: string - accept?: string - children?: FormElement[] -} - -interface FormConfig { - formRef: string - formModel: string - formRules: string - formBtns?: boolean - fields: FormElement[] -} - -/** - * @name: 生成js需要的数据 - * @description: 生成js需要的数据 - * @param {*} conf - * @param {*} type 弹窗或表单 - * @return {*} - */ -export function makeUpJs(conf: FormConfig, type: string): string { - conf = JSON.parse(JSON.stringify(conf)) - const dataList: string[] = [] - const ruleList: string[] = [] - const optionsList: string[] = [] - const propsList: string[] = [] - const methodList: string[] = [] - const uploadVarList: string[] = [] - - conf.fields.forEach((el) => { - buildAttributes( - el, - dataList, - ruleList, - optionsList, - methodList, - propsList, - uploadVarList - ) - }) - - const script = buildexport( - conf, - type, - dataList.join('\n'), - ruleList.join('\n'), - optionsList.join('\n'), - uploadVarList.join('\n'), - propsList.join('\n'), - methodList.join('\n') - ) - - return script -} - -/** - * @name: 生成参数 - * @description: 生成参数,包括表单数据表单验证数据,多选选项数据,上传数据等 - * @return {*} - */ -function buildAttributes( - el: FormElement, - dataList: string[], - ruleList: string[], - optionsList: string[], - methodList: string[], - propsList: string[], - uploadVarList: string[] -): void { - buildData(el, dataList) - buildRules(el, ruleList) - - if (el.options && el.options.length) { - buildOptions(el, optionsList) - if (el.dataType === 'dynamic') { - const model = `${el.vModel}Options` - const options = titleCase(model) - buildOptionMethod(`get${options}`, model, methodList) - } - } - - if (el.props && el.props.props) { - buildProps(el, propsList) - } - - if (el.action && el.tag === 'el-upload') { - uploadVarList.push( - ` - // 上传请求路径 - const ${el.vModel}Action = ref('${el.action}') - // 上传文件列表 - const ${el.vModel}fileList = ref([])` - ) - methodList.push(buildBeforeUpload(el)) - if (!el['auto-upload']) { - methodList.push(buildSubmitUpload(el)) - } - } - - if (el.children) { - el.children.forEach((el2) => { - buildAttributes( - el2, - dataList, - ruleList, - optionsList, - methodList, - propsList, - uploadVarList - ) - }) - } -} - -/** - * @name: 生成表单数据formData - * @description: 生成表单数据formData - * @param {*} conf - * @param {*} dataList 数据列表 - * @return {*} - */ -function buildData(conf: FormElement, dataList: string[]): void { - if (conf.vModel === undefined) return - let defaultValue: string - if (typeof conf.defaultValue === 'string' && !conf.multiple) { - defaultValue = `'${conf.defaultValue}'` - } else { - defaultValue = `${JSON.stringify(conf.defaultValue)}` - } - dataList.push(`${conf.vModel}: ${defaultValue},`) -} - -/** - * @name: 生成表单验证数据rule - * @description: 生成表单验证数据rule - * @param {*} conf - * @param {*} ruleList 验证数据列表 - * @return {*} - */ -function buildRules(conf: FormElement, ruleList: string[]): void { - if (conf.vModel === undefined) return - const rules: string[] = [] - if (trigger[conf.tag as keyof typeof trigger]) { - if (conf.required) { - const type = Array.isArray(conf.defaultValue) ? "type: 'array'," : '' - let message = Array.isArray(conf.defaultValue) - ? `请至少选择一个${conf.vModel}` - : conf.placeholder - if (message === undefined) message = `${conf.label}不能为空` - rules.push( - `{ required: true, ${type} message: '${message}', trigger: '${ - trigger[conf.tag as keyof typeof trigger] - }' }` - ) - } - if (conf.regList && Array.isArray(conf.regList)) { - conf.regList.forEach((item) => { - if (item.pattern) { - rules.push( - `{ pattern: new RegExp(${item.pattern}), message: '${ - item.message - }', trigger: '${trigger[conf.tag as keyof typeof trigger]}' }` - ) - } - }) - } - ruleList.push(`${conf.vModel}: [${rules.join(',')}],`) - } -} - -/** - * @name: 生成选项数据 - * @description: 生成选项数据,单选多选下拉等 - * @param {*} conf - * @param {*} optionsList 选项数据列表 - * @return {*} - */ -function buildOptions(conf: FormElement, optionsList: string[]): void { - if (conf.vModel === undefined) return - if (conf.dataType === 'dynamic') { - conf.options = [] - } - const str = `const ${conf.vModel}Options = ref(${JSON.stringify(conf.options)})` - optionsList.push(str) -} - -/** - * @name: 生成方法 - * @description: 生成方法 - * @param {*} methodName 方法名 - * @param {*} model - * @param {*} methodList 方法列表 - * @return {*} - */ -function buildOptionMethod(methodName: string, model: string, methodList: string[]): void { - const str = `function ${methodName}() { - // TODO 发起请求获取数据 - ${model}.value - }` - methodList.push(str) -} - -/** - * @name: 生成表单组件需要的props设置 - * @description: 生成表单组件需要的props设置,如;级联组件 - * @param {*} conf - * @param {*} propsList - * @return {*} - */ -function buildProps(conf: FormElement, propsList: string[]): void { - if (conf.dataType === 'dynamic') { - const valueKey = (conf as any).valueKey - const labelKey = (conf as any).labelKey - const childrenKey = (conf as any).childrenKey - - valueKey !== 'value' && (conf.props!.props.value = valueKey) - labelKey !== 'label' && (conf.props!.props.label = labelKey) - childrenKey !== 'children' && (conf.props!.props.children = childrenKey) - } - const str = ` - // props设置 - const ${conf.vModel}Props = ref(${JSON.stringify(conf.props!.props)})` - propsList.push(str) -} - -/** - * @name: 生成上传组件的相关内容 - * @description: 生成上传组件的相关内容 - * @param {*} conf - * @return {*} - */ -function buildBeforeUpload(conf: FormElement): string { - const unitNum = units[conf.sizeUnit!] - let rightSizeCode = '' - let acceptCode = '' - const returnList: string[] = [] - - if (conf.fileSize) { - rightSizeCode = `let isRightSize = file.size / ${unitNum} < ${conf.fileSize} - if(!isRightSize){ - proxy.$modal.msgError('文件大小超过 ${conf.fileSize}${conf.sizeUnit}') - }` - returnList.push('isRightSize') - } - - if (conf.accept) { - acceptCode = `let isAccept = new RegExp('${conf.accept}').test(file.type) - if(!isAccept){ - proxy.$modal.msgError('应该选择${conf.accept}类型的文件') - }` - returnList.push('isAccept') - } - - const str = ` - /** - * @name: 上传之前的文件判断 - * @description: 上传之前的文件判断,判断文件大小文件类型等 - * @param {*} file - * @return {*} - */ - function ${conf.vModel}BeforeUpload(file) { - ${rightSizeCode} - ${acceptCode} - return ${returnList.join('&&')} - }` - return returnList.length ? str : '' -} - -/** - * @name: 生成提交表单方法 - * @description: 生成提交表单方法 - * @param {Object} conf vModel 表单ref - * @return {*} - */ -function buildSubmitUpload(conf: FormElement): string { - const str = `function submitUpload() { - this.$refs['${conf.vModel}'].submit() - }` - return str -} - -/** - * @name: 组装js代码 - * @description: 组装js代码方法 - * @return {*} - */ -function buildexport( - conf: FormConfig, - type: string, - data: string, - rules: string, - selectOptions: string, - uploadVar: string, - props: string, - methods: string -): string { - let str = ` - const { proxy } = getCurrentInstance() - const ${conf.formRef} = ref() - const data = reactive({ - ${conf.formModel}: { - ${data} - }, - ${conf.formRules}: { - ${rules} - } - }) - - const {${conf.formModel}, ${conf.formRules}} = toRefs(data) - - ${selectOptions} - - ${uploadVar} - - ${props} - - ${methods} - ` - - if(type === 'dialog') { - str += ` - // 弹窗设置 - const dialogVisible = defineModel() - // 弹窗确认回调 - const emit = defineEmits(['confirm']) - /** - * @name: 弹窗打开后执行 - * @description: 弹窗打开后执行方法 - * @return {*} - */ - function onOpen(){ - - } - /** - * @name: 弹窗关闭时执行 - * @description: 弹窗关闭方法,重置表单 - * @return {*} - */ - function onClose(){ - ${conf.formRef}.value.resetFields() - } - /** - * @name: 弹窗取消 - * @description: 弹窗取消方法 - * @return {*} - */ - function close(){ - dialogVisible.value = false - } - /** - * @name: 弹窗表单提交 - * @description: 弹窗表单提交方法 - * @return {*} - */ - function handelConfirm(){ - ${conf.formRef}.value.validate((valid) => { - if (!valid) return - // TODO 提交表单 - - close() - // 回调父级组件 - emit('confirm') - }) - } - ` - } else { - str += ` - /** - * @name: 表单提交 - * @description: 表单提交方法 - * @return {*} - */ - function submitForm() { - ${conf.formRef}.value.validate((valid) => { - if (!valid) return - // TODO 提交表单 - }) - } - /** - * @name: 表单重置 - * @description: 表单重置方法 - * @return {*} - */ - function resetForm() { - ${conf.formRef}.value.resetFields() - } - ` - } - return str -} diff --git a/frontend/src/utils/generator/render.ts b/frontend/src/utils/generator/render.ts deleted file mode 100644 index c25c1655..00000000 --- a/frontend/src/utils/generator/render.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { defineComponent, h, resolveComponent } from 'vue' -import { makeMap } from '@/utils/index' - -const isAttr = makeMap( - 'accept,accept-charset,accesskey,action,align,alt,async,autocomplete,' + - 'autofocus,autoplay,autosave,bgcolor,border,buffered,challenge,charset,' + - 'checked,cite,class,code,codebase,color,cols,colspan,content,http-equiv,' + - 'name,contenteditable,contextmenu,controls,coords,data,datetime,default,' + - 'defer,dir,dirname,disabled,download,draggable,dropzone,enctype,method,for,' + - 'form,formaction,headers,height,hidden,high,href,hreflang,http-equiv,' + - 'icon,id,ismap,itemprop,keytype,kind,label,lang,language,list,loop,low,' + - 'manifest,max,maxlength,media,method,GET,POST,min,multiple,email,file,' + - 'muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,' + - 'preload,radiogroup,readonly,rel,required,reversed,rows,rowspan,sandbox,' + - 'scope,scoped,seamless,selected,shape,size,type,text,password,sizes,span,' + - 'spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,' + - 'target,title,type,usemap,value,width,wrap' + 'prefix-icon' -) - -const isNotProps = makeMap( - 'layout,prepend,regList,tag,document,changeTag,defaultValue' -) - -interface ComponentConfig { - tag: string - options?: Array<{ label: string; value: any }> - optionType?: string - border?: boolean - 'list-type'?: string - showTip?: boolean - fileSize?: number - sizeUnit?: string - accept?: string - buttonText?: string - default?: string - [key: string]: any -} - -interface ChildFunction { - (h: any, conf: ComponentConfig, key: string): any -} - -interface SlotFunction { - (h: any, conf: ComponentConfig, key: string): () => any -} - -function useVModel(props: any, emit: any) { - return { - modelValue: props.defaultValue, - 'onUpdate:modelValue': (val: any) => emit('update:modelValue', val), - } -} - -const componentChild: Record> = { - 'el-button': { - default(h, conf, key) { - return conf[key] - }, - }, - 'el-select': { - options(h, conf, key) { - return conf.options!.map(item => h(resolveComponent('el-option'), { - label: item.label, - value: item.value, - })) - } - }, - 'el-radio-group': { - options(h, conf, key) { - return conf.optionType === 'button' ? conf.options!.map(item => h(resolveComponent('el-radio-button'), { - label: item.value, - }, () => item.label)) : conf.options!.map(item => h(resolveComponent('el-radio'), { - label: item.value, - border: conf.border, - }, () => item.label)) - } - }, - 'el-checkbox-group': { - options(h, conf, key) { - return conf.optionType === 'button' ? conf.options!.map(item => h(resolveComponent('el-checkbox-button'), { - label: item.value, - }, () => item.label)) : conf.options!.map(item => h(resolveComponent('el-checkbox'), { - label: item.value, - border: conf.border, - }, () => item.label)) - } - }, - 'el-upload': { - 'list-type': (h, conf, key) => { - const option: Record = {} - // if (conf.showTip) { - // tip = h('div', { - // class: "el-upload__tip" - // }, () => '只能上传不超过' + conf.fileSize + conf.sizeUnit + '的' + conf.accept + '文件') - // } - if (conf['list-type'] === 'picture-card') { - return h(resolveComponent('el-icon'), option, () => h(resolveComponent('Plus'))) - } else { - // option.size = "small" - option.type = "primary" - option.icon = "Upload" - return h(resolveComponent('el-button'), option, () => conf.buttonText) - } - }, - } -} - -const componentSlot: Record> = { - 'el-upload': { - 'tip': (h, conf, key) => { - if (conf.showTip) { - return () => h('div', { - class: "el-upload__tip" - }, '只能上传不超过' + conf.fileSize + conf.sizeUnit + '的' + conf.accept + '文件') - } - return () => null - }, - } -} - -export default defineComponent({ - // 使用 render 函数 - render() { - const dataObject: Record = { - attrs: {} as Record, - props: {} as Record, - on: {} as Record, - style: {} as Record - } - const confClone = JSON.parse(JSON.stringify(this.conf)) - const children: any[] = [] - const slot: Record = {} - - const childObjs = componentChild[confClone.tag] - if (childObjs) { - Object.keys(childObjs).forEach(key => { - const childFunc = childObjs[key] - if (confClone[key]) { - children.push(childFunc(h, confClone, key)) - } - }) - } - - const slotObjs = componentSlot[confClone.tag] - if (slotObjs) { - Object.keys(slotObjs).forEach(key => { - const childFunc = slotObjs[key] - if (confClone[key]) { - slot[key] = childFunc(h, confClone, key) - } - }) - } - - Object.keys(confClone).forEach(key => { - const val = confClone[key] - if (dataObject[key]) { - dataObject[key] = val - } else if (isAttr(key)) { - dataObject.attrs[key] = val - } else if (!isNotProps(key)) { - dataObject.props[key] = val - } - }) - - if(children.length > 0){ - slot.default = () => children - } - - return h(resolveComponent(this.conf.tag), - { - modelValue: (this as any).$attrs.modelValue, - ...dataObject.props, - ...dataObject.attrs, - style: { - ...dataObject.style - }, - } - , slot ?? null) - }, - props: { - conf: { - type: Object, - required: true, - }, - } -}) diff --git a/frontend/src/views/gencode/backcode/index.vue b/frontend/src/views/gencode/backcode/index.vue index 140eb958..77bec2c5 100644 --- a/frontend/src/views/gencode/backcode/index.vue +++ b/frontend/src/views/gencode/backcode/index.vue @@ -9,29 +9,9 @@ - - - 查询 重置 - - @@ -288,7 +268,7 @@ v-loading="loading" :data="columns" row-key="id" - :max-height="tableHeight" + max-height="680" highlight--currentrow class="data-table__content" border @@ -555,7 +535,7 @@ 上一步,基础配置 - 保存字段配置 + 保存字段配置 下一步,预览代码 @@ -591,7 +571,6 @@ 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 { formatToDateTime } from "@/utils/dateUtil"; import MenuAPI, { MenuTable } from "@/api/system/menu"; import DictAPI, { DictTable } from "@/api/system/dict"; import { formatTree } from "@/utils/common"; @@ -633,10 +612,7 @@ const loading = ref(false); const total = ref(0); const uniqueId = ref(""); const editVisible = ref(false); -const tableHeight = ref(0); const activeStep = ref(2); -const isExpandable = ref(true); -const isExpand = ref(false); // UI状态 const createTableVisible = ref(false); @@ -724,17 +700,6 @@ const cmOptions: EditorConfiguration = { readOnly: true }; -// 处理日期范围变化 -function handleDateRangeChange(range: [Date, Date]) { - dateRange.value = range; - if (range && range.length === 2) { - queryFormData.start_time = formatToDateTime(range[0]); - queryFormData.end_time = formatToDateTime(range[1]); - } else { - queryFormData.start_time = undefined; - queryFormData.end_time = undefined; - } -} // 工具函数 const { copy } = useClipboard(); @@ -1032,7 +997,7 @@ function handleImportTableSelectionChange(selection: DatabaseTable[]): void { function calculateTableHeight() { // 为了确保表格有足够的高度显示,我们设置一个固定的合理值 // 这里使用400px作为表格高度,这是一个适合大多数屏幕的高度 - tableHeight.value = 680; + } // 修改菜单选项过滤逻辑,添加递归过滤函数 @@ -1204,25 +1169,26 @@ onActivated(async () => { }); // 表单数据 -const info = reactive({ +const info = reactive({ id: undefined, table_name: '', table_comment: '', + sub_table_name: '', + sub_table_fk_name: '', class_name: '', package_name: '', module_name: '', business_name: '', function_name: '', gen_type: '0', - parent_menu_id: undefined, + options: {parent_menu_id: undefined,}, description: '', + parent_menu_id: undefined, + parent_menu_name: '', + pk_column: undefined, + sub_table: undefined, columns: [], sub: false, - tree: false, - crud: true, - params: { - parent_menu_id: undefined, - } }); // 校验规则 @@ -1240,15 +1206,9 @@ const rules = { /** 提交表单 - 保存配置 */ async function submitForm() { - // 验证基本信息表单 - const basicValid = await basicInfo.value?.validate() || false; - if (!basicValid) { - return; - } - - // 验证生成信息表单 - const genValid = await genInfo.value?.validate() || false; - if (!genValid) { + // 检查是否有表ID + if (!info.id) { + ElMessage.error('无效的表ID'); return; } @@ -1261,22 +1221,12 @@ async function submitForm() { return; } - // 设置params参数 - if (info.parent_menu_id) { - info.params = { - parent_menu_id: info.parent_menu_id - }; - } - // 提交表单数据,确保columns是必需的 const tableData = { ...info, columns: info.columns || [] // 确保columns存在 }; - // 清理不需要的字段 - delete (tableData as any).params; - delete (tableData as any).parent_menu_id; const response = await GencodeAPI.updateTable(tableData as GenTableSchema, info.id || 0); if (response?.data?.code === 200) { @@ -1288,7 +1238,6 @@ async function submitForm() { } finally { loading.value = false; } - return false; } diff --git a/frontend/src/views/gencode/webcode/README.md b/frontend/src/views/gencode/webcode/README.md deleted file mode 100644 index b7f55a2b..00000000 --- a/frontend/src/views/gencode/webcode/README.md +++ /dev/null @@ -1,153 +0,0 @@ -# 低代码页面生成器 - -一个基于 Vue3 + TypeScript + Element Plus 的可视化页面生成器,支持拖拽式组件设计和代码生成。 - -## 功能特性 - -- 🎨 **可视化设计**: 拖拽式组件设计,所见即所得 -- 📦 **丰富组件**: 支持所有 Element Plus 组件 -- ⚙️ **属性编辑**: 实时编辑组件属性 -- 💾 **模板管理**: 保存和加载页面模板 -- 🔧 **代码生成**: 生成完整的 Vue3 + TypeScript 代码 -- 📱 **响应式**: 支持移动端适配 - -## 文件结构 - -``` -src/views/gencode/backcode/ -├── index.vue # 主界面 -├── components/ -│ ├── Palette.vue # 左侧组件库 -│ ├── Canvas.vue # 中间画布 -│ ├── CanvasComponent.vue # 画布中的组件 -│ ├── Inspector.vue # 右侧属性面板 -│ └── PropertyEditor.vue # 属性编辑器 -└── utils/ - ├── schema.ts # 组件 Schema 定义 - └── serializer.ts # 代码生成器 -``` - -## 使用方法 - -### 1. 添加组件 -- 从左侧组件库拖拽组件到中间画布 -- 或点击组件快速添加 - -### 2. 编辑组件 -- 点击画布中的组件选中 -- 在右侧属性面板编辑属性 -- 支持样式、事件、插槽等设置 - -### 3. 生成代码 -- 点击顶部"生成代码"按钮 -- 代码会自动复制到剪贴板 -- 可选择下载为 .vue 文件 - -### 4. 保存模板 -- 点击"保存模板"按钮 -- 输入模板名称和描述 -- 模板会保存到本地存储 - -## 支持的组件类型 - -### 基础组件 -- 按钮 (el-button) -- 链接 (el-link) -- 文本 (el-text) -- 图标 (el-icon) - -### 布局组件 -- 行 (el-row) -- 列 (el-col) -- 容器 (el-container) - -### 表单组件 -- 输入框 (el-input) -- 选择器 (el-select) -- 单选框 (el-radio) -- 复选框 (el-checkbox) -- 开关 (el-switch) -- 滑块 (el-slider) -- 日期选择器 (el-date-picker) -- 时间选择器 (el-time-picker) -- 上传 (el-upload) -- 评分 (el-rate) -- 颜色选择器 (el-color-picker) - -### 数据展示组件 -- 卡片 (el-card) -- 表格 (el-table) -- 轮播图 (el-carousel) -- 折叠面板 (el-collapse) -- 描述列表 (el-descriptions) -- 空状态 (el-empty) -- 图片 (el-image) -- 分页 (el-pagination) -- 进度条 (el-progress) -- 结果页 (el-result) -- 骨架屏 (el-skeleton) -- 统计数值 (el-statistic) -- 标签 (el-tag) -- 时间线 (el-timeline) -- 树形控件 (el-tree) - -### 导航组件 -- 固钉 (el-affix) -- 面包屑 (el-breadcrumb) -- 下拉菜单 (el-dropdown) -- 菜单 (el-menu) -- 页面头部 (el-page-header) -- 步骤条 (el-steps) -- 标签页 (el-tabs) - -### 反馈组件 -- 警告提示 (el-alert) -- 抽屉 (el-drawer) -- 加载 (el-loading) -- 消息提示 (el-message) -- 消息确认框 (el-message-box) -- 通知 (el-notification) -- 气泡确认框 (el-popconfirm) -- 弹出框 (el-popover) -- 文字提示 (el-tooltip) - -### 其他组件 -- 回到顶部 (el-backtop) -- 分割线 (el-divider) -- 水印 (el-watermark) - -## 技术栈 - -- **Vue 3**: 使用 Composition API -- **TypeScript**: 完整的类型支持 -- **Element Plus**: UI 组件库 -- **Vite**: 构建工具 -- **SCSS**: 样式预处理器 - -## 开发说明 - -### 添加新组件 -1. 在 `utils/schema.ts` 中添加组件类型 -2. 在 `COMPONENT_CONFIGS` 中配置组件属性 -3. 在 `COMPONENT_CATEGORIES` 中分类组件 - -### 自定义属性编辑器 -1. 在 `PropertyEditor.vue` 中添加新的属性类型 -2. 实现对应的编辑器组件 -3. 更新属性验证逻辑 - -### 扩展代码生成 -1. 在 `serializer.ts` 中添加新的生成逻辑 -2. 支持自定义模板和样式 -3. 添加代码格式化功能 - -## 注意事项 - -- 组件 ID 必须唯一 -- 属性值需要符合 Element Plus 组件规范 -- 生成的代码需要手动验证和测试 -- 复杂布局建议使用栅格系统 - -## 许可证 - -MIT License diff --git a/frontend/src/views/gencode/webcode/components/Canvas.vue b/frontend/src/views/gencode/webcode/components/Canvas.vue deleted file mode 100644 index 38f47548..00000000 --- a/frontend/src/views/gencode/webcode/components/Canvas.vue +++ /dev/null @@ -1,407 +0,0 @@ - -