소스 검색

优化生成

SpringSunYY 5 달 전
부모
커밋
db257755d2

+ 2 - 2
.gitignore

@@ -37,7 +37,7 @@ npm-debug.log*
 yarn-debug.log*
 yarn-error.log*
 **/*.log
-
+ruoyi-test/*
 tests/**/coverage/
 tests/e2e/reports
 selenium-debug.log
@@ -52,4 +52,4 @@ selenium-debug.log
 *.local
 
 package-lock.json
-yarn.lock
+yarn.lock

+ 10 - 4
ruoyi_admin/app.py

@@ -13,21 +13,27 @@ ruoyi = FlaskRuoYi()
 def create_app():
     app = Flask(__name__)
     app.config.from_object(RuoYiConfig)
-    
+
     # 初始化 ruoyi
     ruoyi.init_app(app, PROJECT_ROOT)
-    
+
     # 初始化其他扩展
     from ruoyi_admin.ext import cors, fredis, lm, db
     cors.init_app(app)
     fredis.init_app(app)
     lm.init_app(app)
     db.init_app(app)
-    
+
     # 注册代码生成模块
     from ruoyi_generator import init_app
     init_app(app)
-    
+    # 注册测试模块
+    try:
+        from ruoyi_test import init_app as test_init_app
+        test_init_app(app)
+    except ImportError:
+        print("测试模块未找到或未正确配置")
+
     return app
 
 

+ 10 - 3
ruoyi_generator/domain/entity.py

@@ -42,9 +42,10 @@ class GenTable(BaseEntity):
     columns: Optional[List['GenTableColumn']] = None
     pk_column: Optional['GenTableColumn'] = Field(None, alias='pkColumn')
     # 添加tree相关字段
-    tree_name: Optional[str] = Field(None, alias='treeName')
     tree_code: Optional[str] = Field(None, alias='treeCode')
     tree_parent_code: Optional[str] = Field(None, alias='treeParentCode')
+    tree_name: Optional[str] = Field(None, alias='treeName')
+
     # 分页参数
     page_num: Optional[int] = Field(None, alias='pageNum')
     page_size: Optional[int] = Field(None, alias='pageSize')
@@ -84,7 +85,7 @@ class GenTableColumn(BaseEntity):
         json_encoders = {
             datetime: lambda v: v.strftime(DateUtil.YYYY_MM_DD_HH_MM_SS) if v else None
         },
-        extra = "forbid"
+        extra = "allow"  # 允许动态添加的属性(如list_index)
     )
 
     column_id: Optional[int] = Field(None, alias='columnId')
@@ -113,12 +114,18 @@ class GenTableColumn(BaseEntity):
     # 分页参数
     page_num: Optional[int] = Field(None, alias='pageNum')
     page_size: Optional[int] = Field(None, alias='pageSize')
+    # 动态添加的list_index属性,用于Vue模板
+    list_index: Optional[int] = Field(None, alias='listIndex', exclude=True)
 
     def model_dump(self, **kwargs):
         # 确保使用别名导出
         kwargs.setdefault('by_alias', True)
         kwargs.setdefault('exclude_none', False)  # 确保包含所有字段
-        return super().model_dump(**kwargs)
+        result = super().model_dump(**kwargs)
+        # 确保包含动态添加的list_index属性(如果存在)
+        if hasattr(self, 'list_index') and self.list_index is not None:
+            result['listIndex'] = self.list_index
+        return result
 
     def model_dump_json(self, **kwargs):
         # 确保使用别名导出为JSON

+ 2 - 1
ruoyi_generator/mapper/__init__.py

@@ -372,7 +372,8 @@ class GenTableMapper:
                 else:
                     column.java_type = 'String'
                 
-                column.java_field = StringUtil.to_camel_case(row[0]) if hasattr(StringUtil, 'to_camel_case') else row[0]
+                # 使用 GenUtils.to_camel_case 方法转换字段名
+                column.java_field = GenUtils.to_camel_case(row[0])
                 column.is_pk = '1' if row[5] == 'PRI' else '0'
                 column.is_increment = '1' if row[6] == 'auto_increment' else '0'
                 column.is_required = '0' if row[3] == 'YES' else '1'

+ 4 - 0
ruoyi_generator/ruoyi_generator.py

@@ -55,6 +55,10 @@ class RuoYiGenerator:
         columns = gen_table_column_mapper.select_list_by_table_id(table_id)
         table.columns = columns
         
+        # 设置列的 list_index 属性
+        from ruoyi_generator.util import GenUtils
+        GenUtils.set_column_list_index(table)
+        
         # 设置主键列
         pk_columns = [column for column in columns if column.is_pk == '1']
         if pk_columns:

+ 43 - 4
ruoyi_generator/service/__init__.py

@@ -146,11 +146,22 @@ class GenTableService:
             if table_id > 0:
                 # 保存列信息
                 columns = gen_table_mapper.select_db_table_columns_by_name(table_name)
+                if not columns:
+                    print(f"警告:表 {table_name} 没有字段信息,可能表不存在或查询失败")
+                else:
+                    print(f"表 {table_name} 找到 {len(columns)} 个字段")
                 for column in columns:
-                    column.table_id = table_id
-                    column.create_by = "admin"
-                    column.create_time = datetime.now()
-                    gen_table_column_mapper.insert(column)
+                    try:
+                        column.table_id = table_id
+                        column.create_by = "admin"
+                        column.create_time = datetime.now()
+                        # 确保 java_field 已设置
+                        if not column.java_field:
+                            column.java_field = GenUtils.to_camel_case(column.column_name)
+                        gen_table_column_mapper.insert(column)
+                    except Exception as e:
+                        print(f"插入字段 {column.column_name} 失败: {e}")
+                        continue
                 success_count += 1
         return success_count
 
@@ -277,6 +288,16 @@ class GenTableService:
 
         # 查询列信息
         gen_table.columns = gen_table_column_mapper.select_list_by_table_id(gen_table.table_id)
+        
+        # 设置列的 list_index 属性
+        GenUtils.set_column_list_index(gen_table)
+        
+        # 设置主键列
+        pk_columns = [column for column in gen_table.columns if column.is_pk == '1']
+        if pk_columns:
+            gen_table.pk_column = pk_columns[0]
+        else:
+            gen_table.pk_column = None
 
         # 生成代码
         return GenUtils.generator_code(gen_table).getvalue()
@@ -298,6 +319,14 @@ class GenTableService:
             if gen_table:
                 # 查询列信息
                 gen_table.columns = gen_table_column_mapper.select_list_by_table_id(gen_table.table_id)
+                # 设置列的 list_index 属性
+                GenUtils.set_column_list_index(gen_table)
+                # 设置主键列
+                pk_columns = [column for column in gen_table.columns if column.is_pk == '1']
+                if pk_columns:
+                    gen_table.pk_column = pk_columns[0]
+                else:
+                    gen_table.pk_column = None
                 gen_tables.append(gen_table)
 
         # 生成代码
@@ -320,6 +349,16 @@ class GenTableService:
 
         # 查询列信息
         gen_table.columns = gen_table_column_mapper.select_list_by_table_id(table_id)
+        
+        # 设置列的 list_index 属性
+        GenUtils.set_column_list_index(gen_table)
+        
+        # 设置主键列
+        pk_columns = [column for column in gen_table.columns if column.is_pk == '1']
+        if pk_columns:
+            gen_table.pk_column = pk_columns[0]
+        else:
+            gen_table.pk_column = None
 
         # 预览代码
         return GenUtils.preview_code(gen_table)

+ 3 - 19
ruoyi_generator/service/column_service.py

@@ -21,25 +21,9 @@ class GenTableColumnService:
             Tuple[List[GenTableColumn], int]: 代码生成表列列表和总数
         """
         columns = gen_table_column_mapper.select_list_by_table_id(table_id)
-        # 实现分页逻辑
-        page_num = 1
-        page_size = 10
-        # 从flask的g对象或request中获取分页参数
-        try:
-            from flask import g, request
-            if hasattr(g, 'criterian_meta') and g.criterian_meta.page:
-                page_num = g.criterian_meta.page.page_num
-                page_size = g.criterian_meta.page.page_size
-            elif request and request.args.get('pageNum') and request.args.get('pageSize'):
-                page_num = int(request.args.get('pageNum', 1))
-                page_size = int(request.args.get('pageSize', 10))
-        except Exception:
-            pass
-            
-        start = (page_num - 1) * page_size
-        end = start + page_size
-        paginated_columns = columns[start:end]
-        return paginated_columns, len(columns)
+        # 编辑表时应该显示所有字段,不需要分页
+        # 返回所有字段和总数
+        return columns, len(columns) if columns else 0
 
     def insert_gen_table_column(self, gen_table_column: GenTableColumn) -> bool:
         """

+ 31 - 0
ruoyi_generator/sql/generator.sql

@@ -0,0 +1,31 @@
+create table tb_address_info
+(
+    id           bigint auto_increment comment '编号'
+        primary key,
+    parent_id    bigint      not null comment '父级编号',
+    name         varchar(64) not null comment '位置名称',
+    address_type char        not null comment '类型',
+    image_info   varchar(5012) null comment '图片',
+    file_info    varchar(5012) null comment '文件',
+    remark       text        not null comment '备注',
+    status       char        not null comment '状态',
+    user_id      bigint      not null comment '创建人',
+    create_time  datetime    not null comment '创建时间',
+    update_time  datetime null comment '更新时间'
+) comment '位置表' row_format = DYNAMIC;
+
+create table tb_schedule_info
+(
+    id          bigint auto_increment comment '编号'
+        primary key,
+    name        varchar(255) not null comment '名称',
+    peoples     int          not null comment '人数',
+    file        varchar(2550) null comment '文件',
+    image       varchar(2550) null comment '图片',
+    status      char         not null comment '状态',
+    remark      text null comment '备注',
+    user_id     bigint       not null comment '创建人',
+    create_time datetime     not null comment '创建时间',
+    update_time datetime null comment '更新时间'
+) comment '课程表' row_format = DYNAMIC;
+

+ 147 - 41
ruoyi_generator/util.py

@@ -70,6 +70,7 @@ class GenUtils:
             po_name = f"{table.class_name}PO"
             return f"{package_path}/domain/{po_name}.py"
         elif 'vue/index.vue' in template_file:
+            # 无论是树表还是普通表,Vue文件名都是index.vue
             return f"vue/{table.business_name}/index.vue"
         elif 'js/api.js' in template_file:
             return f"js/api/{table.business_name}.js"
@@ -143,6 +144,35 @@ class GenUtils:
         return GenUtils.substring_before(clean_table_name, "_") if "_" in clean_table_name else clean_table_name
 
     @staticmethod
+    def get_import_path(package_name: str, module_type: str, class_name: str = None) -> str:
+        """
+        生成导入路径
+        
+        Args:
+            package_name (str): 包名,如 "com.yy.project" 或 "ruoyi_generator"
+            module_type (str): 模块类型,如 "domain", "service", "mapper", "controller"
+            class_name (str): 类名(可选,用于PO导入)
+            
+        Returns:
+            str: 导入路径,Python包名保持点分隔格式
+        """
+        if not package_name:
+            return f"ruoyi_generator.{module_type}"
+        
+        # Python导入路径使用点分隔,保持原样
+        # 例如: "com.yy.project" -> "com.yy.project"
+        # 例如: "ruoyi_generator" -> "ruoyi_generator" (保持不变)
+        python_package = package_name
+        
+        # 生成导入路径
+        if module_type == "domain" and class_name:
+            return f"{python_package}.domain.po"
+        elif module_type == "domain":
+            return f"{python_package}.domain.entity"
+        else:
+            return f"{python_package}.{module_type}"
+
+    @staticmethod
     def to_camel_case(name: str) -> str:
         """
         将下划线命名转换为驼峰命名
@@ -210,24 +240,42 @@ class GenUtils:
         Returns:
             BytesIO: 生成的代码文件
         """
+        # 设置列的 list_index 属性
+        GenUtils.set_column_list_index(table)
+        
+        # 设置主键列
+        pk_columns = [column for column in table.columns if column.is_pk == '1']
+        if pk_columns:
+            table.pk_column = pk_columns[0]
+        else:
+            table.pk_column = None
+        
         # 获取模板目录
         template_dir = os.path.join(os.path.dirname(__file__), 'vm')
         
+        # 定义核心模板文件
+        core_templates = [
+            'py/entity.py.vm',
+            'py/po.py.vm', 
+            'py/controller.py.vm',
+            'py/service.py.vm',
+            'py/mapper.py.vm',
+            'js/api.js.vm',
+            'sql/menu.sql.vm'
+        ]
+        
+        # 根据表类型添加相应的Vue模板
+        if table.tpl_category == 'tree':
+            core_templates.append('vue/index-tree.vue.vm')
+        else:
+            core_templates.append('vue/index.vue.vm')
+        
         # 创建内存中的ZIP文件
         zip_buffer = BytesIO()
         
         with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
-            # 定义需要处理的模板文件
-            template_files = []
-            for root, dirs, files in os.walk(template_dir):
-                for file in files:
-                    if file.endswith('.vm'):
-                        full_path = os.path.join(root, file)
-                        relative_path = os.path.relpath(full_path, template_dir)
-                        template_files.append(relative_path)
-            
-            # 处理每个模板文件
-            for relative_path in template_files:
+            # 处理每个核心模板文件
+            for relative_path in core_templates:
                 template_path = os.path.join(template_dir, relative_path)
                 if os.path.exists(template_path):
                     # 读取模板内容
@@ -239,7 +287,8 @@ class GenUtils:
                         context = {
                             'table': table,
                             'datetime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
-                            'underscore': to_underscore  # 添加自定义过滤器
+                            'underscore': to_underscore,  # 添加自定义过滤器
+                            'get_import_path': GenUtils.get_import_path  # 添加导入路径生成函数
                         }
                         
                         # 使用Jinja2渲染模板
@@ -272,29 +321,47 @@ class GenUtils:
         Returns:
             BytesIO: 生成的代码文件
         """
+        # 为每个表设置列的 list_index 属性和主键列
+        for table in tables:
+            GenUtils.set_column_list_index(table)
+            # 设置主键列
+            pk_columns = [column for column in table.columns if column.is_pk == '1']
+            if pk_columns:
+                table.pk_column = pk_columns[0]
+            else:
+                table.pk_column = None
+        
+        # 定义核心模板文件
+        core_templates = [
+            'py/entity.py.vm',
+            'py/po.py.vm', 
+            'py/controller.py.vm',
+            'py/service.py.vm',
+            'py/mapper.py.vm',
+            'js/api.js.vm',
+            'sql/menu.sql.vm'
+        ]
+        
         # 创建内存中的ZIP文件
         zip_buffer = BytesIO()
         
+        # 获取模板目录
+        template_dir = os.path.join(os.path.dirname(__file__), 'vm')
+        
         with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file:
             # 跟踪已添加的文件名以避免重复
             added_files = set()
             
             # 处理每个表
             for table in tables:
-                # 获取模板目录
-                template_dir = os.path.join(os.path.dirname(__file__), 'vm')
+                # 根据表类型添加相应的Vue模板
+                if table.tpl_category == 'tree':
+                    current_templates = core_templates + ['vue/index-tree.vue.vm']
+                else:
+                    current_templates = core_templates + ['vue/index.vue.vm']
                 
-                # 定义需要处理的模板文件
-                template_files = []
-                for root, dirs, files in os.walk(template_dir):
-                    for file in files:
-                        if file.endswith('.vm'):
-                            full_path = os.path.join(root, file)
-                            relative_path = os.path.relpath(full_path, template_dir)
-                            template_files.append(relative_path)
-                
-                # 处理每个模板文件
-                for relative_path in template_files:
+                # 处理每个核心模板文件
+                for relative_path in current_templates:
                     template_path = os.path.join(template_dir, relative_path)
                     if os.path.exists(template_path):
                         # 读取模板内容
@@ -306,7 +373,8 @@ class GenUtils:
                             context = {
                                 'table': table,
                                 'datetime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
-                                'underscore': to_underscore  # 添加自定义过滤器
+                                'underscore': to_underscore,  # 添加自定义过滤器
+                                'get_import_path': GenUtils.get_import_path  # 添加导入路径生成函数
                             }
                             
                             # 使用Jinja2渲染模板
@@ -341,6 +409,24 @@ class GenUtils:
         return zip_buffer
         
     @staticmethod
+    def set_column_list_index(table: GenTable):
+        """
+        为表的列设置 list_index 属性,用于 Vue 模板中的 columns 数组索引
+        
+        Args:
+            table (GenTable): 表信息
+        """
+        if not table.columns:
+            return
+        
+        list_index = 0
+        for column in table.columns:
+            if column.is_list == '1':
+                # 使用 setattr 动态添加属性
+                setattr(column, 'list_index', list_index)
+                list_index += 1
+
+    @staticmethod
     def preview_code(table: GenTable) -> dict:
         """
         预览代码
@@ -351,23 +437,41 @@ class GenUtils:
         Returns:
             dict: 预览代码
         """
+        # 设置列的 list_index 属性
+        GenUtils.set_column_list_index(table)
+        
+        # 设置主键列
+        pk_columns = [column for column in table.columns if column.is_pk == '1']
+        if pk_columns:
+            table.pk_column = pk_columns[0]
+        else:
+            table.pk_column = None
+        
         # 获取模板目录
         template_dir = os.path.join(os.path.dirname(__file__), 'vm')
         
         # 存储预览代码的字典
         preview_data = {}
         
-        # 定义需要预览的模板文件(严格按照项目要求)
-        template_files = []
-        for root, dirs, files in os.walk(template_dir):
-            for file in files:
-                if file.endswith('.vm'):
-                    full_path = os.path.join(root, file)
-                    relative_path = os.path.relpath(full_path, template_dir)
-                    template_files.append(relative_path)
-        
-        # 处理每个模板文件
-        for relative_path in template_files:
+        # 定义需要预览的核心模板文件
+        core_templates = [
+            'py/entity.py.vm',
+            'py/po.py.vm', 
+            'py/controller.py.vm',
+            'py/service.py.vm',
+            'py/mapper.py.vm',
+            'js/api.js.vm',
+            'sql/menu.sql.vm'
+        ]
+        
+        # 根据表类型添加相应的Vue模板,但预览时都使用index.vue.vm作为文件名
+        if table.tpl_category == 'tree':
+            core_templates.append('vue/index-tree.vue.vm')
+        else:
+            core_templates.append('vue/index.vue.vm')
+        
+        # 处理每个核心模板文件
+        for relative_path in core_templates:
             template_path = os.path.join(template_dir, relative_path)
             if os.path.exists(template_path):
                 # 读取模板内容
@@ -379,7 +483,8 @@ class GenUtils:
                     context = {
                         'table': table,
                         'datetime': datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
-                        'underscore': to_underscore  # 添加自定义过滤器
+                        'underscore': to_underscore,  # 添加自定义过滤器
+                        'get_import_path': GenUtils.get_import_path  # 添加导入路径生成函数
                     }
                     
                     # 使用Jinja2渲染模板
@@ -389,8 +494,9 @@ class GenUtils:
                     # 存储渲染后的内容
                     preview_data[relative_path] = rendered_content
                 except Exception as e:
-                    # 如果渲染失败,存储原始模板内容
-                    with open(template_path, 'r', encoding='utf-8') as f:
-                        preview_data[relative_path] = f.read()
+                    # 如果渲染失败,存储错误信息
+                    preview_data[relative_path] = f"模板渲染失败: {str(e)}"
+            else:
+                preview_data[relative_path] = "模板文件不存在"
         
         return preview_data

+ 56 - 0
ruoyi_generator/vm/README.md.vm

@@ -0,0 +1,56 @@
+# {{ table.function_name }}
+
+## 简介
+
+{% if table.remark != '' %}{{ table.remark }}{% else %}{{ table.function_name }}{% endif %}
+
+## 联系方式
+
+- 作者:{{ table.function_author }}
+- 邮箱:example@example.com
+
+## 功能说明
+
+该模块包含以下主要功能:
+
+1. {{ table.function_name }}信息的增删改查操作
+2. 支持分页查询和条件筛选
+3. 提供RESTful API接口
+4. 基于角色的访问控制(RBAC)
+
+## 数据库设计
+
+### 表名
+{{ table.table_name }}
+
+### 表结构
+{% for column in table.columns %}
+- {{ column.column_name }} ({{ column.column_type }}): {{ column.column_comment }}
+{% endfor %}
+
+## API接口
+
+| 接口名称 | 请求方式 | 接口地址 | 说明 |
+|---------|---------|---------|------|
+| 查询列表 | GET | /{{ table.module_name }}/{{ table.business_name }}/list | 分页查询{{ table.function_name }}列表 |
+| 查询详情 | GET | /{{ table.module_name }}/{{ table.business_name }}/{id} | 根据ID查询{{ table.function_name }}详情 |
+| 新增数据 | POST | /{{ table.module_name }}/{{ table.business_name }} | 新增一条{{ table.function_name }}记录 |
+| 修改数据 | PUT | /{{ table.module_name }}/{{ table.business_name }}/{id} | 根据ID修改{{ table.function_name }}记录 |
+| 删除数据 | DELETE | /{{ table.module_name }}/{{ table.business_name }}/{ids} | 根据ID删除{{ table.function_name }}记录 |
+
+## 权限说明
+
+本模块涉及以下权限:
+
+1. {{ table.module_name }}:{{ table.business_name }}:list - 查询权限
+2. {{ table.module_name }}:{{ table.business_name }}:add - 新增权限
+3. {{ table.module_name }}:{{ table.business_name }}:edit - 修改权限
+4. {{ table.module_name }}:{{ table.business_name }}:remove - 删除权限
+5. {{ table.module_name }}:{{ table.business_name }}:export - 导出权限
+
+## 使用说明
+
+1. 确保数据库表 `{{ table.table_name }}` 已创建并包含相应字段
+2. 在系统菜单中添加对应的菜单项和权限配置
+3. 启动服务后可通过API接口进行访问
+4. 前端页面位于 `{{ table.module_name }}/{{ table.business_name }}/index` 路径下

+ 71 - 0
ruoyi_generator/vm/js/api.js.vm

@@ -0,0 +1,71 @@
+import request from '@/utils/request'
+
+// 查询{{ table.function_name }}列表
+export function list{{ table.class_name }}(query) {
+  return request({
+    url: '/{{ table.module_name }}/{{ table.business_name }}/list',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询{{ table.function_name }}详细
+export function get{{- table.class_name }}({{ underscore(table.pk_column.java_field) if table.pk_column }}) {
+  return request({
+    url: '/{{ table.module_name }}/{{ table.business_name }}/' + {{- underscore(table.pk_column.java_field) if table.pk_column }},
+    method: 'get'
+  })
+}
+
+// 新增{{ table.function_name }}
+export function add{{ table.class_name }}(data) {
+  return request({
+    url: '/{{ table.module_name }}/{{ table.business_name }}',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改{{ table.function_name }}
+export function update{{ table.class_name }}(data) {
+  return request({
+    url: '/{{ table.module_name }}/{{ table.business_name }}/' + data.{{ underscore(table.pk_column.java_field) if table.pk_column }},
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除{{ table.function_name }}
+export function del{{- table.class_name }}({{ underscore(table.pk_column.java_field) if table.pk_column }}) {
+  return request({
+    url: '/{{ table.module_name }}/{{ table.business_name }}/' + {{- underscore(table.pk_column.java_field) if table.pk_column }},
+    method: 'delete'
+  })
+}
+
+// 导出{{ table.function_name }}
+export function export{{ table.class_name }}(query) {
+  return request({
+    url: '/{{ table.module_name }}/{{ table.business_name }}/export',
+    method: 'post',
+    params: query
+  })
+}
+
+// 下载{{ table.function_name }}导入模板
+export function importTemplate() {
+  return request({
+    url: '/{{ table.module_name }}/{{ table.business_name }}/importTemplate',
+    method: 'post',
+    responseType: 'blob'
+  })
+}
+
+// 导入{{ table.function_name }}
+export function importData(data) {
+  return request({
+    url: '/{{ table.module_name }}/{{ table.business_name }}/importData',
+    method: 'post',
+    data: data
+  })
+}

+ 71 - 14
ruoyi_generator/vm/py/controller.py.vm

@@ -1,12 +1,14 @@
 from flask import request, Blueprint
-from ruoyi_common.descriptor.validator import QueryValidator, BodyValidator, PathValidator
-from ruoyi_common.descriptor.serializer import JsonSerializer
-from ruoyi_framework.descriptor.permission import HasPerm, PreAuthorize
-from ruoyi_generator.domain.po import {{ table.class_name }}PO
-from ruoyi_generator.domain.entity import {{ table.class_name }}
-from ruoyi_generator.service.{{ underscore(table.class_name) }}_service import {{ table.class_name }}Service
+
 from ruoyi_common.base.model import AjaxResponse, TableResponse
 from ruoyi_common.constant import HttpStatus
+from ruoyi_common.descriptor.serializer import JsonSerializer
+from ruoyi_common.descriptor.validator import QueryValidator, BodyValidator, PathValidator, FileDownloadValidator, FileUploadValidator
+from ruoyi_common.utils.base import ExcelUtil
+from ruoyi_framework.descriptor.permission import HasPerm, PreAuthorize
+from {{ get_import_path(table.package_name, 'domain', table.class_name) }} import {{ table.class_name }}PO
+from {{ get_import_path(table.package_name, 'domain') }} import {{ table.class_name }}
+from {{ get_import_path(table.package_name, 'service') }}.{{ underscore(table.class_name) }}_service import {{ table.class_name }}Service
 
 # 创建蓝图
 gen = Blueprint('{{ underscore(table.class_name) }}', __name__, url_prefix='/{{ table.module_name }}/{{ table.business_name }}')
@@ -27,9 +29,8 @@ def {{ table.business_name }}_list(dto: {{ table.class_name }}PO):
     {{ table.business_name }}s, total = {{ table.class_name|lower }}_service.select_{{ table.business_name }}_list({{ table.class_name|lower }})
     return TableResponse(code=HttpStatus.SUCCESS, msg='查询成功', rows={{ table.business_name }}s, total=total)
 
-{# 修复第35行的语法错误 #}
 {% if table.pk_column %}
-@gen.route('/<int:{{ underscore(table.pk_column.java_field) }}>', methods=['GET'])
+@gen.route('/<int:{{ underscore(table.pk_column.java_field) }}}', methods=['GET'])
 @PathValidator()
 @PreAuthorize(HasPerm('{{ table.module_name }}:{{ table.business_name }}:query'))
 @JsonSerializer()
@@ -51,11 +52,10 @@ def add_{{ table.business_name }}(dto: {{ table.class_name }}PO):
         if hasattr({{ table.class_name|lower }}, attr):
             setattr({{ table.class_name|lower }}, attr, getattr(dto, attr))
     result = {{ table.class_name|lower }}_service.insert_{{ table.business_name }}({{ table.class_name|lower }})
-    return AjaxResponse.from_success(msg='操作成功' if result else '操作失败')
+    return AjaxResponse.from_success(msg='新增成功' if result > 0 else '新增失败')
 
-{# 修复PUT路由的语法错误 #}
 {% if table.pk_column %}
-@gen.route('/<int:{{ underscore(table.pk_column.java_field) }}>', methods=['PUT'])
+@gen.route('/<int:{{ underscore(table.pk_column.java_field) }}}', methods=['PUT'])
 @BodyValidator()
 @PathValidator()
 @PreAuthorize(HasPerm('{{ table.module_name }}:{{ table.business_name }}:edit'))
@@ -69,7 +69,7 @@ def update_{{ table.business_name }}({{ underscore(table.pk_column.java_field) }
             setattr({{ table.class_name|lower }}, attr, getattr(dto, attr))
     {{ table.class_name|lower }}.{{ underscore(table.pk_column.java_field) }} = {{ underscore(table.pk_column.java_field) }}
     result = {{ table.class_name|lower }}_service.update_{{ table.business_name }}({{ table.class_name|lower }})
-    return AjaxResponse.from_success(msg='操作成功' if result else '操作失败')
+    return AjaxResponse.from_success(msg='修改成功' if result > 0 else '修改失败')
 {% endif %}
 
 {% if table.pk_column %}
@@ -82,7 +82,64 @@ def delete_{{ table.business_name }}(ids: str):
     try:
         id_list = [int(id) for id in ids.split(',')]
         result = {{ table.class_name|lower }}_service.delete_{{ table.business_name }}_by_ids(id_list)
-        return AjaxResponse.from_success(msg='操作成功' if result else '操作失败')
+        return AjaxResponse.from_success(msg='删除成功' if result > 0 else '删除失败')
     except Exception as e:
-        return AjaxResponse.from_error(msg=f'操作失败: {str(e)}')
+        return AjaxResponse.from_error(msg=f'删除失败: {str(e)}')
 {% endif %}
+
+@gen.route('/export', methods=['POST'])
+@FileDownloadValidator()
+@QueryValidator()
+@PreAuthorize(HasPerm('{{ table.module_name }}:{{ table.business_name }}:export'))
+def export_{{ table.business_name }}(dto: {{ table.class_name }}PO):
+    """导出{{ table.function_name }}列表"""
+    {{ table.class_name|lower }} = {{ table.class_name }}()
+    # 转换PO到Entity对象
+    for attr in dto.model_fields.keys():
+        if hasattr({{ table.class_name|lower }}, attr):
+            setattr({{ table.class_name|lower }}, attr, getattr(dto, attr))
+    {{ table.business_name }}s, total = {{ table.class_name|lower }}_service.select_{{ table.business_name }}_list({{ table.class_name|lower }})
+    # 使用ExcelUtil导出Excel文件
+    excel_util = ExcelUtil({{ table.class_name }}PO)
+    return excel_util.export_response({{ table.business_name }}s, "{{ table.function_name }}数据")
+
+@gen.route('/importTemplate', methods=['POST'])
+@FileDownloadValidator()
+@PreAuthorize(HasPerm('{{ table.module_name }}:{{ table.business_name }}:import'))
+def import_template():
+    """下载{{ table.function_name }}导入模板"""
+    excel_util = ExcelUtil({{ table.class_name }}PO)
+    return excel_util.import_template_response("{{ table.function_name }}导入模板")
+
+@gen.route('/importData', methods=['POST'])
+@FileUploadValidator()
+@PreAuthorize(HasPerm('{{ table.module_name }}:{{ table.business_name }}:import'))
+@JsonSerializer()
+def import_data():
+    """导入{{ table.function_name }}数据"""
+    file = request.files.get('file')
+    if not file:
+        return AjaxResponse.from_error(msg='上传文件不能为空')
+    
+    # 获取更新支持参数
+    update_support = request.form.get('updateSupport', '0') != '0'
+    
+    try:
+        # 使用ExcelUtil读取Excel文件
+        excel_util = ExcelUtil({{ table.class_name }}PO)
+        po_list = excel_util.import_file(file, sheetname="{{ table.function_name }}数据")
+        
+        # 转换为Entity对象列表
+        {{ table.business_name }}_list = []
+        for po in po_list:
+            {{ table.class_name|lower }} = {{ table.class_name }}()
+            for attr in po.model_fields.keys():
+                if hasattr({{ table.class_name|lower }}, attr):
+                    setattr({{ table.class_name|lower }}, attr, getattr(po, attr))
+            {{ table.business_name }}_list.append({{ table.class_name|lower }})
+        
+        # 调用Service层处理导入逻辑
+        msg = {{ table.class_name|lower }}_service.import_{{ table.business_name }}({{ table.business_name }}_list, update_support)
+        return AjaxResponse.from_success(msg=msg)
+    except Exception as e:
+        return AjaxResponse.from_error(msg=f'导入失败: {str(e)}')

+ 35 - 15
ruoyi_generator/vm/py/entity.py.vm

@@ -3,26 +3,46 @@
 # @FileName: {{ table.class_name }}.py
 # @Time    : {{ datetime }}
 
-from typing import Optional
+from typing import Optional, Union
 from datetime import datetime
 from ruoyi_common.base.model import BaseEntity
-from pydantic import ConfigDict, Field
+from sqlalchemy import Column, String, Integer, Float, Boolean, DateTime, Text, BigInteger
 from ruoyi_common.utils.base import DateUtil
 
 class {{ table.class_name }}(BaseEntity):
     """
-    {{ table.table_comment }}
+    {{ table.table_comment }}对象
     """
-    model_config = ConfigDict(
-        from_attributes=True,
-        json_encoders = {
-            datetime: lambda v: v.strftime(DateUtil.YYYY_MM_DD_HH_MM_SS) if v else None
-        },
-        alias_generator = lambda field: ''.join(['_' + c.lower() if c.isupper() else c for c in field]).lstrip('_')
-    )
+    
+    __tablename__ = '{{ table.table_name }}'
+{%- for column in table.columns %}
+{%- if column.java_type == 'String' or column.java_type == 'str' %}
+    {{ underscore(column.java_field) }} = Column(String(255), default=None, doc="{{ column.column_comment }}")
+{%- elif column.java_type == 'Integer' or column.java_type == 'int' %}
+    {{ underscore(column.java_field) }} = Column(Integer, default=None, doc="{{ column.column_comment }}")
+{%- elif column.java_type == 'Long' %}
+    {{ underscore(column.java_field) }} = Column(BigInteger, default=None, doc="{{ column.column_comment }}")
+{%- elif column.java_type == 'Float' or column.java_type == 'Double' %}
+    {{ underscore(column.java_field) }} = Column(Float, default=None, doc="{{ column.column_comment }}")
+{%- elif column.java_type == 'Boolean' or column.java_type == 'bool' %}
+    {{ underscore(column.java_field) }} = Column(Boolean, default=None, doc="{{ column.column_comment }}")
+{%- else %}
+    {{ underscore(column.java_field) }} = Column(String(255), default=None, doc="{{ column.column_comment }}")
+{%- endif %}
+{%- endfor %}
 
-{% for column in table.columns %}    {{ underscore(column.java_field) }}: Optional[{{ column.java_type }}] = Field(None, alias='{{ column.java_field }}')
-{% endfor %}
-    # 分页参数
-    page_num: Optional[int] = Field(None, alias='pageNum')
-    page_size: Optional[int] = Field(None, alias='pageSize')
+    def __init__(self, **kwargs):
+        super().__init__(**kwargs)
+        # 初始化所有字段
+{%- for column in table.columns %}
+        self.{{ underscore(column.java_field) }} = kwargs.get('{{ underscore(column.java_field) }}', None)
+{%- endfor %}
+
+    def to_dict(self):
+        """转换为字典"""
+        result = super().to_dict()
+        # 添加实体类特定字段
+{%- for column in table.columns %}
+        result['{{ underscore(column.java_field) }}'] = self.{{ underscore(column.java_field) }}
+{%- endfor %}
+        return result

+ 51 - 7
ruoyi_generator/vm/py/mapper.py.vm

@@ -4,11 +4,13 @@
 # @Time    : {{ datetime }}
 
 from typing import List
-from ruoyi_generator.domain.entity import {{ table.class_name }}
-from ruoyi_generator.ext import db
-from sqlalchemy import select, update, delete, text
 from datetime import datetime
 
+from sqlalchemy import select, update, delete, func
+
+from ruoyi_admin.ext import db
+from {{ get_import_path(table.package_name, 'domain') }} import {{ table.class_name }}
+
 class {{ table.class_name }}Mapper:
     """{{ table.function_name }}Mapper"""
 
@@ -26,6 +28,15 @@ class {{ table.class_name }}Mapper:
         try:
             # 构建查询条件
             stmt = select({{ table.class_name }})
+{% for column in table.columns %}
+{% if column.is_query and column.query_type == 'EQ' %}
+            if {{ table.business_name }}.{{ underscore(column.java_field) }} is not None:
+                stmt = stmt.where({{ table.class_name }}.{{ underscore(column.java_field) }} == {{ table.business_name }}.{{ underscore(column.java_field) }})
+{% elif column.is_query and column.query_type == 'LIKE' %}
+            if {{ table.business_name }}.{{ underscore(column.java_field) }}:
+                stmt = stmt.where({{ table.class_name }}.{{ underscore(column.java_field) }}.like("%" + str({{ table.business_name }}.{{ underscore(column.java_field) }}) + "%"))
+{% endif %}
+{% endfor %}
 
             # 添加分页条件
             if {{ table.business_name }}.page_num and {{ table.business_name }}.page_size:
@@ -39,23 +50,54 @@ class {{ table.class_name }}Mapper:
             return []
 
     @staticmethod
-    def select_by_id({{ underscore(table.pk_column.java_field) if table.pk_column }}: int) -> {{ table.class_name }}:
+    def select_count({{ table.business_name }}: {{ table.class_name }}) -> int:
+        """
+        查询{{ table.function_name }}总数
+
+        Args:
+            {{ table.business_name }} ({{ table.class_name }}): {{ table.function_name }}对象
+
+        Returns:
+            int: {{ table.function_name }}总数
+        """
+        try:
+            stmt = select(func.count({{ table.class_name }}.{{ underscore(table.pk_column.java_field) if table.pk_column }}))
+{% for column in table.columns %}
+{% if column.is_query and column.query_type == 'EQ' %}
+            if {{ table.business_name }}.{{ underscore(column.java_field) }} is not None:
+                stmt = stmt.where({{ table.class_name }}.{{ underscore(column.java_field) }} == {{ table.business_name }}.{{ underscore(column.java_field) }})
+{% elif column.is_query and column.query_type == 'LIKE' %}
+            if {{ table.business_name }}.{{ underscore(column.java_field) }}:
+                stmt = stmt.where({{ table.class_name }}.{{ underscore(column.java_field) }}.like("%" + str({{ table.business_name }}.{{ underscore(column.java_field) }}) + "%"))
+{% endif %}
+{% endfor %}
+
+            result = db.session.execute(stmt).scalar()
+            return result if result else 0
+        except Exception as e:
+            print(f"查询{{ table.function_name }}总数出错: {e}")
+            return 0
+
+    {% if table.pk_column %}
+    @staticmethod
+    def select_by_id({{ underscore(table.pk_column.java_field) }}: int) -> {{ table.class_name }}:
         """
         根据ID查询{{ table.function_name }}
 
         Args:
-            {{ underscore(table.pk_column.java_field) if table.pk_column }} (int): {{ table.pk_column.column_comment if table.pk_column }}
+            {{ underscore(table.pk_column.java_field) }} (int): {{ table.pk_column.column_comment }}
 
         Returns:
             {{ table.class_name }}: {{ table.function_name }}对象
         """
         try:
-            stmt = select({{ table.class_name }}).where({{ table.class_name }}.{{ underscore(table.pk_column.java_field) if table.pk_column }} == {{ underscore(table.pk_column.java_field) if table.pk_column }})
+            stmt = select({{ table.class_name }}).where({{ table.class_name }}.{{ underscore(table.pk_column.java_field) }} == {{ underscore(table.pk_column.java_field) }})
             result = db.session.execute(stmt).scalar_one_or_none()
             return result
         except Exception as e:
             print(f"根据ID查询{{ table.function_name }}出错: {e}")
             return None
+    {% endif %}
 
     @staticmethod
     def insert({{ table.business_name }}: {{ table.class_name }}) -> int:
@@ -82,6 +124,7 @@ class {{ table.class_name }}Mapper:
             print(f"新增{{ table.function_name }}出错: {e}")
             return 0
 
+    {% if table.pk_column %}
     @staticmethod
     def update({{ table.business_name }}: {{ table.class_name }}) -> int:
         """
@@ -118,7 +161,7 @@ class {{ table.class_name }}Mapper:
             int: 删除的记录数
         """
         try:
-            stmt = delete({{ table.class_name }}).where({{ table.class_name }}.{{ underscore(table.pk_column.java_field) if table.pk_column }}.in_(ids))
+            stmt = delete({{ table.class_name }}).where({{ table.class_name }}.{{ underscore(table.pk_column.java_field) }}.in_(ids))
             result = db.session.execute(stmt)
             db.session.commit()
             return result.rowcount
@@ -126,3 +169,4 @@ class {{ table.class_name }}Mapper:
             db.session.rollback()
             print(f"批量删除{{ table.function_name }}出错: {e}")
             return 0
+    {% endif %}

+ 43 - 0
ruoyi_generator/vm/py/po.py.vm

@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# @Author  : {{ table.function_author }}
+# @FileName: {{ table.class_name }}PO.py
+# @Time    : {{ datetime }}
+
+from typing import Optional, Union
+from datetime import datetime
+
+from pydantic import ConfigDict, Field
+
+from ruoyi_common.base.model import BaseEntity
+from ruoyi_common.utils.base import DateUtil
+
+class {{ table.class_name }}PO(BaseEntity):
+    """
+    {{ table.table_comment }}PO对象
+    """
+    model_config = ConfigDict(
+        from_attributes=True,
+        json_encoders = {
+            datetime: lambda v: v.strftime(DateUtil.YYYY_MM_DD_HH_MM_SS) if v else None
+        },
+        alias_generator = lambda field: ''.join(['_' + c.lower() if c.isupper() else c for c in field]).lstrip('_')
+    )
+
+{%- for column in table.columns %}
+{%- if column.java_type == 'String' or column.java_type == 'str' %}
+    {{ underscore(column.java_field) }}: Optional[str] = Field(None, alias='{{ column.java_field }}')
+{%- elif column.java_type == 'Integer' or column.java_type == 'int' %}
+    {{ underscore(column.java_field) }}: Optional[int] = Field(None, alias='{{ column.java_field }}')
+{%- elif column.java_type == 'Long' %}
+    {{ underscore(column.java_field) }}: Optional[int] = Field(None, alias='{{ column.java_field }}')
+{%- elif column.java_type == 'Float' or column.java_type == 'Double' %}
+    {{ underscore(column.java_field) }}: Optional[float] = Field(None, alias='{{ column.java_field }}')
+{%- elif column.java_type == 'Boolean' or column.java_type == 'bool' %}
+    {{ underscore(column.java_field) }}: Optional[bool] = Field(None, alias='{{ column.java_field }}')
+{%- else %}
+    {{ underscore(column.java_field) }}: Optional[str] = Field(None, alias='{{ column.java_field }}')
+{%- endif %}
+{%- endfor %}
+    # 分页参数
+    page_num: Optional[int] = Field(None, alias='pageNum')
+    page_size: Optional[int] = Field(None, alias='pageSize')

+ 79 - 6
ruoyi_generator/vm/py/service.py.vm

@@ -4,8 +4,9 @@
 # @Time    : {{ datetime }}
 
 from typing import List, Tuple
-from ruoyi_generator.domain.entity import {{ table.class_name }}
-from ruoyi_generator.mapper.{{ underscore(table.class_name) }}_mapper import {{ table.class_name }}Mapper
+
+from {{ get_import_path(table.package_name, 'domain') }} import {{ table.class_name }}
+from {{ get_import_path(table.package_name, 'mapper') }}.{{ underscore(table.class_name) }}_mapper import {{ table.class_name }}Mapper
 
 class {{ table.class_name }}Service:
     """{{ table.function_name }}服务类"""
@@ -20,19 +21,23 @@ class {{ table.class_name }}Service:
         Returns:
             Tuple[List[{{ table.class_name }}], int]: {{ table.function_name }}列表和总数
         """
-        return {{ table.class_name }}Mapper.select_list({{ table.business_name }})
+        list_result = {{ table.class_name }}Mapper.select_list({{ table.business_name }})
+        count_result = {{ table.class_name }}Mapper.select_count({{ table.business_name }})
+        return list_result, count_result
 
-    def select_{{ table.business_name }}_by_id(self, {{ underscore(table.pk_column.java_field) if table.pk_column }}: int) -> {{ table.class_name }}:
+    {% if table.pk_column %}
+    def select_{{ table.business_name }}_by_id(self, {{ underscore(table.pk_column.java_field) }}: int) -> {{ table.class_name }}:
         """
         根据ID查询{{ table.function_name }}
 
         Args:
-            {{ underscore(table.pk_column.java_field) if table.pk_column }} (int): {{ table.pk_column.column_comment if table.pk_column }}
+            {{ underscore(table.pk_column.java_field) }} (int): {{ table.pk_column.column_comment }}
 
         Returns:
             {{ table.class_name }}: {{ table.function_name }}对象
         """
-        return {{ table.class_name }}Mapper.select_by_id({{ underscore(table.pk_column.java_field) if table.pk_column }})
+        return {{ table.class_name }}Mapper.select_by_id({{ underscore(table.pk_column.java_field) }})
+    {% endif %}
 
     def insert_{{ table.business_name }}(self, {{ table.business_name }}: {{ table.class_name }}) -> int:
         """
@@ -46,6 +51,7 @@ class {{ table.class_name }}Service:
         """
         return {{ table.class_name }}Mapper.insert({{ table.business_name }})
 
+    {% if table.pk_column %}
     def update_{{ table.business_name }}(self, {{ table.business_name }}: {{ table.class_name }}) -> int:
         """
         修改{{ table.function_name }}
@@ -57,7 +63,9 @@ class {{ table.class_name }}Service:
             int: 更新的记录数
         """
         return {{ table.class_name }}Mapper.update({{ table.business_name }})
+    {% endif %}
 
+    {% if table.pk_column %}
     def delete_{{ table.business_name }}_by_ids(self, ids: List[int]) -> int:
         """
         批量删除{{ table.function_name }}
@@ -69,3 +77,68 @@ class {{ table.class_name }}Service:
             int: 删除的记录数
         """
         return {{ table.class_name }}Mapper.delete_by_ids(ids)
+    {% endif %}
+
+    def import_{{ table.business_name }}(self, {{ table.business_name }}_list: List[{{ table.class_name }}], update_support: bool = False) -> str:
+        """
+        导入{{ table.function_name }}数据
+
+        Args:
+            {{ table.business_name }}_list (List[{{ table.class_name }}]): {{ table.function_name }}列表
+            update_support (bool): 是否更新已存在的数据
+
+        Returns:
+            str: 导入结果消息
+        """
+        if not {{ table.business_name }}_list:
+            raise Exception("导入{{ table.function_name }}数据不能为空")
+        
+        success_count = 0
+        failure_count = 0
+        failure_msg = []
+        
+        for index, {{ table.business_name }} in enumerate({{ table.business_name }}_list):
+            try:
+                {% if table.pk_column %}
+                # 检查是否已存在
+                existing = {{ table.class_name }}Mapper.select_by_id({{ table.business_name }}.{{ underscore(table.pk_column.java_field) }})
+                if existing:
+                    if update_support:
+                        # 更新数据
+                        result = {{ table.class_name }}Mapper.update({{ table.business_name }})
+                        if result > 0:
+                            success_count += 1
+                        else:
+                            failure_count += 1
+                            failure_msg.append(f"第{index + 2}行更新失败")
+                    else:
+                        failure_count += 1
+                        failure_msg.append(f"第{index + 2}行数据已存在")
+                else:
+                    # 新增数据
+                    result = {{ table.class_name }}Mapper.insert({{ table.business_name }})
+                    if result > 0:
+                        success_count += 1
+                    else:
+                        failure_count += 1
+                        failure_msg.append(f"第{index + 2}行导入失败")
+                {% else %}
+                # 没有主键,直接插入
+                result = {{ table.class_name }}Mapper.insert({{ table.business_name }})
+                if result > 0:
+                    success_count += 1
+                else:
+                    failure_count += 1
+                    failure_msg.append(f"第{index + 2}行导入失败")
+                {% endif %}
+            except Exception as e:
+                failure_count += 1
+                failure_msg.append(f"第{index + 2}行导入失败: {str(e)}")
+        
+        if failure_count == 0:
+            return f'成功导入{success_count}条数据'
+        else:
+            error_msg = f'成功导入{success_count}条,失败{failure_count}条'
+            if failure_msg:
+                error_msg += f'。失败原因:{"; ".join(failure_msg[:5])}'
+            raise Exception(error_msg)

+ 22 - 0
ruoyi_generator/vm/sql/menu.sql.vm

@@ -0,0 +1,22 @@
+-- 菜单SQL
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ('{{ table.function_name }}', {{ table.parent_menu_id }}, 1, '{{ table.business_name }}', '{{ table.module_name }}/{{ table.business_name }}/index', 1, 0, 'C', '0', '0', '{{ table.module_name }}:{{ table.business_name }}:list', '#', 'admin', SYSDATE(), '', NULL, '{{ table.function_name }}菜单');
+
+-- 按钮父菜单ID
+SELECT @parentId := LAST_INSERT_ID();
+
+-- 按钮SQL
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ('{{ table.function_name }}查询', @parentId, 1,  '#', '', 1, 0, 'F', '0', '0', '{{ table.module_name }}:{{ table.business_name }}:query',        '#', 'admin', SYSDATE(), '', NULL, '');
+
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ('{{ table.function_name }}新增', @parentId, 2,  '#', '', 1, 0, 'F', '0', '0', '{{ table.module_name }}:{{ table.business_name }}:add',          '#', 'admin', SYSDATE(), '', NULL, '');
+
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ('{{ table.function_name }}修改', @parentId, 3,  '#', '', 1, 0, 'F', '0', '0', '{{ table.module_name }}:{{ table.business_name }}:edit',          '#', 'admin', SYSDATE(), '', NULL, '');
+
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ('{{ table.function_name }}删除', @parentId, 4,  '#', '', 1, 0, 'F', '0', '0', '{{ table.module_name }}:{{ table.business_name }}:remove',       '#', 'admin', SYSDATE(), '', NULL, '');
+
+INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
+VALUES ('{{ table.function_name }}导出', @parentId, 5,  '#', '', 1, 0, 'F', '0', '0', '{{ table.module_name }}:{{ table.business_name }}:export',       '#', 'admin', SYSDATE(), '', NULL, '');

+ 440 - 0
ruoyi_generator/vm/vue/index-tree.vue.vm

@@ -0,0 +1,440 @@
+{% macro getColumnDictType(column) -%}
+  {%- if column.dict_type != '' -%}{{ column.dict_type }}
+  {%- else -%}null
+  {%- endif -%}
+{%- endmacro %}
+
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+{% for column in table.columns %}
+{% if column.is_query %}
+      <el-form-item label="{{ column.column_comment }}" prop="{{ underscore(column.java_field) }}">
+  {% if column.html_type == 'input' %}
+        <el-input
+          v-model="queryParams.{{ underscore(column.java_field) }}"
+          placeholder="请输入{{ column.column_comment }}"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+  {% elif column.html_type == 'select' and column.dict_type != '' %}
+        <el-select v-model="queryParams.{{ underscore(column.java_field) }}" placeholder="请选择{{ column.column_comment }}" clearable>
+          <el-option
+            v-for="dict in {{ getColumnDictType(column) }}Options"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+  {% elif column.html_type == 'datetime' %}
+        <el-date-picker
+          v-model="daterange{{ column.java_field }}"
+          value-format="yyyy-MM-dd"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        ></el-date-picker>
+  {% endif %}
+      </el-form-item>
+{% endif %}
+{% endfor %}
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['{{ table.module_name }}:{{ table.business_name }}:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          plain
+          icon="el-icon-sort"
+          size="mini"
+          @click="toggleExpandAll"
+        >展开/折叠</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
+    </el-row>
+
+    <el-table
+      v-if="refreshTable"
+      v-loading="loading"
+      :data="{{ table.business_name }}List"
+      row-key="{{ table.tree_code }}"
+      :default-expand-all="isExpandAll"
+      :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
+    >
+{% set index = 0 %}
+{% for column in table.columns %}
+{% if column.is_list %}
+  {% if column.is_pk %}
+  {% elif column.html_type == 'datetime' %}
+      <el-table-column label="{{ column.column_comment }}" align="center" v-if="columns[{{ index }}].visible" prop="{{ underscore(column.java_field) }}" width="180">
+        <template slot-scope="scope">
+          <span>{{ parseTime(scope.row.{{ underscore(column.java_field) }}, '{y}-{m}-{d}') }}</span>
+        </template>
+      </el-table-column>
+  {% elif column.html_type == 'imageUpload' %}
+      <el-table-column label="{{ column.column_comment }}" align="center" v-if="columns[{{ index }}].visible" prop="{{ underscore(column.java_field) }}" width="100">
+        <template slot-scope="scope">
+          <image-preview :src="scope.row.{{ underscore(column.java_field) }}" :width="50" :height="50"/>
+        </template>
+      </el-table-column>
+  {% elif column.dict_type != '' %}
+      <el-table-column label="{{ column.column_comment }}" align="center" v-if="columns[{{ index }}].visible" prop="{{ underscore(column.java_field) }}">
+        <template slot-scope="scope">
+    {% if column.html_type == 'checkbox' %}
+          <dict-tag :options="dict.type.{{ getColumnDictType(column) }}" :value="scope.row.{{ underscore(column.java_field) }} ? scope.row.{{ underscore(column.java_field) }}.split(',') : []"/>
+    {% else %}
+          <dict-tag :options="dict.type.{{ getColumnDictType(column) }}" :value="scope.row.{{ underscore(column.java_field) }}"/>
+    {% endif %}
+        </template>
+      </el-table-column>
+  {% else %}
+      <el-table-column label="{{ column.column_comment }}" align="center" :show-overflow-tooltip="true" v-if="columns[{{ index }}].visible" prop="{{ underscore(column.java_field) }}" />
+  {% endif %}
+  {% set index = index + 1 %}
+{% endif %}
+{% endfor %}
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['{{ table.module_name }}:{{ table.business_name }}:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-plus"
+            @click="handleAdd(scope.row)"
+            v-hasPermi="['{{ table.module_name }}:{{ table.business_name }}:add']"
+          >新增</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['{{ table.module_name }}:{{ table.business_name }}:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 添加或修改{{ table.function_name }}对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+{% for column in table.columns %}
+{% if column.is_insert and not column.is_pk %}
+        <el-form-item label="{{ column.column_comment }}" prop="{{ underscore(column.java_field) }}">
+  {% if table.tree_parent_code != '' and column.java_field == table.tree_parent_code %}
+          <treeselect v-model="form.{{ underscore(table.tree_parent_code) }}" :options="{{ table.business_name }}Options" :normalizer="normalizer" placeholder="请选择{{ column.column_comment }}" />
+  {% elif column.html_type == 'input' %}
+          <el-input v-model="form.{{ underscore(column.java_field) }}" placeholder="请输入{{ column.column_comment }}" />
+  {% elif column.html_type == 'textarea' %}
+          <el-input v-model="form.{{ underscore(column.java_field) }}" type="textarea" placeholder="请输入内容" />
+  {% elif column.html_type == 'select' %}
+          <el-select v-model="form.{{ underscore(column.java_field) }}" placeholder="请选择{{ column.column_comment }}">
+            <el-option
+              v-for="dict in {{ getColumnDictType(column) }}Options"
+              :key="dict.value"
+              :label="dict.label"
+    {% if column.java_type == 'Integer' or column.java_type == 'Long' %}
+              :value="parseInt(dict.value)"
+    {% else %}
+              :value="dict.value"
+    {% endif %}
+            ></el-option>
+          </el-select>
+  {% elif column.html_type == 'radio' %}
+          <el-radio-group v-model="form.{{ underscore(column.java_field) }}">
+            <el-radio
+              v-for="dict in {{ getColumnDictType(column) }}Options"
+              :key="dict.value"
+    {% if column.java_type == 'Integer' or column.java_type == 'Long' %}
+              :label="parseInt(dict.value)"
+    {% else %}
+              :label="dict.value"
+    {% endif %}
+            >{{ dict.label }}</el-radio>
+          </el-radio-group>
+  {% elif column.html_type == 'checkbox' %}
+          <el-checkbox-group v-model="form.{{ underscore(column.java_field) }}">
+            <el-checkbox
+              v-for="dict in {{ getColumnDictType(column) }}Options"
+              :key="dict.value"
+              :label="dict.value"
+            >{{ dict.label }}</el-checkbox>
+          </el-checkbox-group>
+  {% elif column.html_type == 'datetime' %}
+          <el-date-picker clearable
+            v-model="form.{{ underscore(column.java_field) }}"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择{{ column.column_comment }}">
+          </el-date-picker>
+  {% elif column.html_type == 'imageUpload' %}
+          <image-upload v-model="form.{{ underscore(column.java_field) }}"/>
+  {% elif column.html_type == 'fileUpload' %}
+          <file-upload v-model="form.{{ underscore(column.java_field) }}"/>
+  {% endif %}
+        </el-form-item>
+{% endif %}
+{% endfor %}
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { list{{ table.class_name }}, get{{ table.class_name }}, del{{ table.class_name }}, add{{ table.class_name }}, update{{ table.class_name }} } from "@/api/{{ table.module_name }}/{{ table.business_name }}";
+import Treeselect from "@riophae/vue-treeselect";
+import "@riophae/vue-treeselect/dist/vue-treeselect.css";
+
+export default {
+  name: "{{ table.class_name }}",
+{% if dicts %}
+  dicts: [{{ dicts }}],
+{% endif %}
+  components: {
+    Treeselect
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // {{ table.function_name }}表格数据
+      {{ table.business_name }}List: [],
+      // {{ table.function_name }}树选项
+      {{ table.business_name }}Options: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 是否展开,默认全部展开
+      isExpandAll: true,
+      // 重新渲染表格状态
+      refreshTable: true,
+      // 表格列信息
+      columns: [
+{% set index = 0 %}
+{% for column in table.columns %}
+{% if column.is_list and not column.is_pk %}
+        { key: {{ index }}, label: '{{ column.column_comment }}', visible: true },
+  {% set index = index + 1 %}
+{% endif %}
+{% endfor %}
+      ],
+{% for column in table.columns %}
+{% if column.dict_type != '' %}
+      // {{ column.column_comment }}字典
+      {{ getColumnDictType(column) }}Options: [],
+{% endif %}
+{% endfor %}
+{% for column in table.columns %}
+{% if column.html_type == 'datetime' %}
+      // {{ column.column_comment }}时间范围
+      daterange{{ column.java_field }}: [],
+{% endif %}
+{% endfor %}
+      // 查询参数
+      queryParams: {
+{% for column in table.columns %}
+{% if column.is_query %}
+        {{ underscore(column.java_field) }}: null,
+{% endif %}
+{% endfor %}
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+{% for column in table.columns %}
+{% if column.is_required %}
+        {{ underscore(column.java_field) }}: [
+          { required: true, message: "{{ column.column_comment }}不能为空", trigger: {% if column.html_type == 'select' or column.html_type == 'radio' %}"change"{% else %}"blur"{% endif %} }
+        ],
+{% endif %}
+{% endfor %}
+      }
+    };
+  },
+  created() {
+{% set dictExists = false %}
+{% for column in table.columns %}
+{% if column.dict_type != '' %}
+    {% if not dictExists %}
+    this.getDicts("{{ getColumnDictType(column) }}").then(response => {
+      this.{{ getColumnDictType(column) }}Options = response.data;
+    });
+    {% set dictExists = true %}
+    {% endif %}
+{% endif %}
+{% endfor %}
+    this.getList();
+  },
+  methods: {
+    /** 查询{{ table.function_name }}列表 */
+    getList() {
+      this.loading = true;
+{% for column in table.columns %}
+{% if column.html_type == 'datetime' %}
+      this.queryParams.params = {};
+      if (null != this.daterange{{ column.java_field }} && '' != this.daterange{{ column.java_field }}.toString()) {
+        this.queryParams.params["begin{{ column.java_field }}"] = this.daterange{{ column.java_field }}[0];
+        this.queryParams.params["end{{ column.java_field }}"] = this.daterange{{ column.java_field }}[1];
+      }
+{% endif %}
+{% endfor %}
+      list{{ table.class_name }}(this.queryParams).then(response => {
+        this.{{ table.business_name }}List = this.handleTree(response.data, "{{ table.tree_code }}", "{{ table.tree_parent_code }}");
+        this.loading = false;
+      });
+    },
+    /** 转换{{ table.function_name }}数据结构 */
+    normalizer(node) {
+      if (node.children && !node.children.length) {
+        delete node.children;
+      }
+      return {
+        id: node.{{ table.tree_code }},
+        label: node.{{ table.tree_name }},
+        children: node.children
+      };
+    },
+    /** 查询{{ table.function_name }}下拉树结构 */
+    getTreeselect() {
+      list{{ table.class_name }}().then(response => {
+        this.{{ table.business_name }}Options = [];
+        const data = { {{ table.tree_code }}: 0, {{ table.tree_name }}: '顶级节点', children: [] };
+        data.children = this.handleTree(response.data, "{{ table.tree_code }}", "{{ table.tree_parent_code }}");
+        this.{{ table.business_name }}Options.push(data);
+      });
+    },
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+{% for column in table.columns %}
+  {% if column.html_type == 'checkbox' %}
+        {{ underscore(column.java_field) }}: [],
+  {% else %}
+        {{ underscore(column.java_field) }}: null,
+  {% endif %}
+{% endfor %}
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+{% for column in table.columns %}
+{% if column.html_type == 'datetime' %}
+      this.daterange{{ column.java_field }} = [];
+{% endif %}
+{% endfor %}
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 新增按钮操作 */
+    handleAdd(row) {
+      this.reset();
+      this.getTreeselect();
+      if (row != null && row.{{ table.tree_code }}) {
+        this.form.{{ underscore(table.tree_parent_code) }} = row.{{ table.tree_code }};
+      } else {
+        this.form.{{ underscore(table.tree_parent_code) }} = 0;
+      }
+      this.open = true;
+      this.title = "添加{{ table.function_name }}";
+    },
+    /** 展开/折叠操作 */
+    toggleExpandAll() {
+      this.refreshTable = false;
+      this.isExpandAll = !this.isExpandAll;
+      this.$nextTick(() => {
+        this.refreshTable = true;
+      });
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      this.getTreeselect();
+      if (row != null) {
+        this.form.{{ underscore(table.tree_parent_code) }} = row.{{ underscore(table.tree_parent_code) }};
+      }
+      get{{ table.class_name }}(row.{{ underscore(table.pk_column.java_field) }}).then(response => {
+        this.form = response.data;
+{% for column in table.columns %}
+{% if column.html_type == 'checkbox' %}
+        this.form.{{ underscore(column.java_field) }} = this.form.{{ underscore(column.java_field) }}.split(",");
+{% endif %}
+{% endfor %}
+        this.open = true;
+        this.title = "修改{{ table.function_name }}";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+{% for column in table.columns %}
+{% if column.html_type == 'checkbox' %}
+          this.form.{{ underscore(column.java_field) }} = this.form.{{ underscore(column.java_field) }}.join(",");
+{% endif %}
+{% endfor %}
+          if (this.form.{{ underscore(table.pk_column.java_field) }} != null) {
+            update{{ table.class_name }}(this.form).then(response => {
+              this.$modal.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            add{{ table.class_name }}(this.form).then(response => {
+              this.$modal.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      this.$modal.confirm('是否确认删除{{ table.function_name }}编号为"' + row.{{ underscore(table.pk_column.java_field) }} + '"的数据项?').then(function() {
+        return del{{ table.class_name }}(row.{{ underscore(table.pk_column.java_field) }});
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => {});
+    }
+  }
+};
+</script>

+ 502 - 0
ruoyi_generator/vm/vue/index.vue.vm

@@ -0,0 +1,502 @@
+{% macro getColumnDictType(column) %}
+  {% if column.dict_type != '' %}{{ column.dict_type }}
+  {% else %}null
+  {% endif %}
+{% endmacro %}
+
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
+{%- for column in table.columns %}
+{%- if column.is_query %}
+      <el-form-item label="{{ column.column_comment }}" prop="{{ underscore(column.java_field) }}">
+  {%- if column.html_type == 'input' %}
+        <el-input
+          v-model="queryParams.{{ underscore(column.java_field) }}"
+          placeholder="请输入{{ column.column_comment }}"
+          clearable
+          @keyup.enter.native="handleQuery"
+        />
+  {%- elif column.html_type == 'select' and column.dict_type != '' %}
+        <el-select v-model="queryParams.{{ underscore(column.java_field) }}" placeholder="请选择{{ column.column_comment }}" clearable>
+          <el-option
+            v-for="dict in {{ getColumnDictType(column) }}Options"
+            :key="dict.value"
+            :label="dict.label"
+            :value="dict.value"
+          />
+        </el-select>
+  {%- elif column.html_type == 'datetime' %}
+        <el-date-picker
+          v-model="daterange{{ column.java_field }}"
+          value-format="yyyy-MM-dd"
+          type="daterange"
+          range-separator="-"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+        ></el-date-picker>
+  {%- endif %}
+      </el-form-item>
+{%- endif %}
+{%- endfor %}
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-plus"
+          size="mini"
+          @click="handleAdd"
+          v-hasPermi="['{{ table.module_name }}:{{ table.business_name }}:add']"
+        >新增</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          :disabled="single"
+          @click="handleUpdate"
+          v-hasPermi="['{{ table.module_name }}:{{ table.business_name }}:edit']"
+        >修改</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="danger"
+          plain
+          icon="el-icon-delete"
+          size="mini"
+          :disabled="multiple"
+          @click="handleDelete"
+          v-hasPermi="['{{ table.module_name }}:{{ table.business_name }}:remove']"
+        >删除</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          @click="handleExport"
+          v-hasPermi="['{{ table.module_name }}:{{ table.business_name }}:export']"
+        >导出</el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="info"
+          plain
+          icon="el-icon-upload2"
+          size="mini"
+          @click="handleImport"
+          v-hasPermi="['{{ table.module_name }}:{{ table.business_name }}:import']"
+        >导入</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="columns"></right-toolbar>
+    </el-row>
+
+    <el-table v-loading="loading" :data="{{ table.business_name }}List" @selection-change="handleSelectionChange">
+      <el-table-column type="selection" width="55" align="center" />
+{%- for column in table.columns %}
+{%- if column.is_list %}
+  {%- if column.is_pk %}
+      <el-table-column label="{{ column.column_comment }}" align="center" v-if="columns[{{ column.list_index }}].visible" prop="{{ underscore(column.java_field) }}" >
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-view"
+            @click="handleView(scope.row)"
+            v-hasPermi="['{{ table.module_name }}:{{ table.business_name }}:query']"
+          >{{ column.column_comment }}</el-button>
+        </template>
+      </el-table-column>
+  {%- elif column.html_type == 'imageUpload' %}
+      <el-table-column label="{{ column.column_comment }}" align="center" v-if="columns[{{ column.list_index }}].visible" prop="{{ underscore(column.java_field) }}" width="100">
+        <template slot-scope="scope">
+          <image-preview :src="scope.row.{{ underscore(column.java_field) }}" :width="50" :height="50"/>
+        </template>
+      </el-table-column>
+  {%- elif column.dict_type != '' %}
+      <el-table-column label="{{ column.column_comment }}" align="center" v-if="columns[{{ column.list_index }}].visible" prop="{{ underscore(column.java_field) }}" :formatter="dict_{{ underscore(column.java_field) }}_format" />
+  {%- elif column.html_type == 'datetime' %}
+      <el-table-column label="{{ column.column_comment }}" align="center" v-if="columns[{{ column.list_index }}].visible" prop="{{ underscore(column.java_field) }}" width="180">
+        <template slot-scope="scope">
+          <span>{{ "{{ scope.row." }}{{ underscore(column.java_field) }}{{ " }}" }}</span>
+        </template>
+      </el-table-column>
+  {%- else %}
+      <el-table-column label="{{ column.column_comment }}" align="center" v-if="columns[{{ column.list_index }}].visible" prop="{{ underscore(column.java_field) }}" />
+  {%- endif %}
+{%- endif %}
+{%- endfor %}
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template slot-scope="scope">
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-edit"
+            @click="handleUpdate(scope.row)"
+            v-hasPermi="['{{ table.module_name }}:{{ table.business_name }}:edit']"
+          >修改</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-delete"
+            @click="handleDelete(scope.row)"
+            v-hasPermi="['{{ table.module_name }}:{{ table.business_name }}:remove']"
+          >删除</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 添加或修改{{ table.function_name }}对话框 -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+{%- for column in table.columns %}
+{%- if column.is_insert %}
+        <el-form-item label="{{ column.column_comment }}" prop="{{ underscore(column.java_field) }}">
+  {%- if column.html_type == 'input' %}
+          <el-input v-model="form.{{ underscore(column.java_field) }}" placeholder="请输入{{ column.column_comment }}" />
+  {%- elif column.html_type == 'textarea' %}
+          <el-input v-model="form.{{ underscore(column.java_field) }}" type="textarea" placeholder="请输入内容" />
+  {%- elif column.html_type == 'select' %}
+          <el-select v-model="form.{{ underscore(column.java_field) }}" placeholder="请选择{{ column.column_comment }}">
+            <el-option
+              v-for="dict in {{ getColumnDictType(column) }}Options"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            ></el-option>
+          </el-select>
+  {%- elif column.html_type == 'radio' %}
+          <el-radio-group v-model="form.{{ underscore(column.java_field) }}">
+            <el-radio
+              v-for="dict in {{ getColumnDictType(column) }}Options"
+              :key="dict.value"
+              :label="dict.value"
+            >{{ dict.label }}</el-radio>
+          </el-radio-group>
+  {%- elif column.html_type == 'checkbox' %}
+          <el-checkbox-group v-model="form.{{ underscore(column.java_field) }}">
+            <el-checkbox
+              v-for="dict in {{ getColumnDictType(column) }}Options"
+              :key="dict.value"
+              :label="dict.value"
+            >{{ dict.label }}</el-checkbox>
+          </el-checkbox-group>
+  {%- elif column.html_type == 'datetime' %}
+          <el-date-picker clearable
+            v-model="form.{{ underscore(column.java_field) }}"
+            type="date"
+            value-format="yyyy-MM-dd"
+            placeholder="选择{{ column.column_comment }}">
+          </el-date-picker>
+  {%- elif column.html_type == 'imageUpload' %}
+          <image-upload v-model="form.{{ underscore(column.java_field) }}"/>
+  {%- elif column.html_type == 'fileUpload' %}
+          <file-upload v-model="form.{{ underscore(column.java_field) }}"/>
+  {%- endif %}
+        </el-form-item>
+{%- endif %}
+{%- endfor %}
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 导入对话框 -->
+    <el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
+      <el-upload
+        ref="upload"
+        :limit="1"
+        accept=".xlsx, .xls"
+        :headers="upload.headers"
+        :action="upload.url"
+        :disabled="upload.isUploading"
+        :on-progress="handleFileUploadProgress"
+        :on-success="handleFileSuccess"
+        :auto-upload="false"
+        drag
+      >
+        <i class="el-icon-upload"></i>
+        <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
+        <div class="el-upload__tip text-center" slot="tip">
+          <div class="el-upload__tip" slot="tip">
+            <el-checkbox v-model="upload.updateSupport" /> 是否更新已经存在的{{ table.function_name }}数据
+          </div>
+          <span>仅允许导入xls、xlsx格式文件。</span>
+          <el-link type="primary" :underline="false" style="font-size:12px;vertical-align: baseline;" @click="importTemplate">下载模板</el-link>
+        </div>
+      </el-upload>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitFileForm">确 定</el-button>
+        <el-button @click="upload.open = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { list{{ table.class_name }}, get{{ table.class_name }}, del{{ table.class_name }}, add{{ table.class_name }}, update{{ table.class_name }}, export{{ table.class_name }}, importTemplate, importData } from "@/api/{{ table.module_name }}/{{ table.business_name }}";
+import { getToken } from "@/utils/auth";
+
+export default {
+  name: "{{ table.class_name }}",
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // {{ table.function_name }}表格数据
+      {{ table.business_name }}List: [],
+      // 表格列信息
+      columns: [
+{%- for column in table.columns %}
+{%- if column.is_list %}
+        { key: {{ column.list_index }}, label: '{{ column.column_comment }}', visible: true }{% if not loop.last %},{% endif %}
+{%- endif %}
+{%- endfor %}
+      ],
+{%- for column in table.columns %}
+{%- if column.dict_type != '' %}
+      // {{ column.column_comment }}字典
+      {{ getColumnDictType(column) }}Options: [],
+{%- endif %}
+{%- endfor %}
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+{%- for column in table.columns %}
+{%- if column.html_type == 'datetime' %}
+      // {{ column.column_comment }}时间范围
+      daterange{{ column.java_field }}: [],
+{%- endif %}
+{%- endfor %}
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+{%- for column in table.columns %}
+{%- if column.is_query %}
+        {{ underscore(column.java_field) }}: null{% if not loop.last %},{% endif %}
+{%- endif %}
+{%- endfor %}
+      },
+      // 表单参数
+      form: {},
+      // 导入参数
+      upload: {
+        // 是否显示弹出层(导入)
+        open: false,
+        // 弹出层标题(导入)
+        title: "",
+        // 是否禁用上传
+        isUploading: false,
+        // 是否更新已经存在的{{ table.function_name }}数据
+        updateSupport: 0,
+        // 设置上传的请求头部
+        headers: { Authorization: "Bearer " + getToken() },
+        // 上传的地址
+        url: process.env.VUE_APP_BASE_API + "/{{ table.module_name }}/{{ table.business_name }}/importData"
+      },
+      // 表单校验
+      rules: {
+{%- for column in table.columns %}
+{%- if column.is_required %}
+        {{ underscore(column.java_field) }}: [
+          { required: true, message: "{{ column.column_comment }}不能为空", trigger: "blur" }
+        ]{% if not loop.last %},{% endif %}
+{%- endif %}
+{%- endfor %}
+      }
+    };
+  },
+  created() {
+{%- set dictExists = false %}
+{%- for column in table.columns %}
+{%- if column.dict_type != '' %}
+    {%- if not dictExists %}
+    this.getDicts("{{ getColumnDictType(column) }}").then(response => {
+      this.{{ getColumnDictType(column) }}Options = response.data;
+    });
+    {%- set dictExists = true %}
+    {%- endif %}
+{%- endif %}
+{%- endfor %}
+    this.getList();
+  },
+  methods: {
+    /** 查询{{ table.function_name }}列表 */
+    getList() {
+      this.loading = true;
+{%- set has_datetime = false %}
+{%- for column in table.columns %}
+{%- if column.html_type == 'datetime' %}
+{%- if not has_datetime %}
+      this.queryParams.params = {};
+{%- set has_datetime = true %}
+{%- endif %}
+      if (null != this.daterange{{ column.java_field }} && '' != this.daterange{{ column.java_field }}.toString()) {
+        this.queryParams.params["begin{{ column.java_field }}"] = this.daterange{{ column.java_field }}[0];
+        this.queryParams.params["end{{ column.java_field }}"] = this.daterange{{ column.java_field }}[1];
+      }
+{%- endif %}
+{%- endfor %}
+      list{{ table.class_name }}(this.queryParams).then(response => {
+        this.{{ table.business_name }}List = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+{%- for column in table.columns %}
+{%- if column.dict_type != '' %}
+    // {{ column.column_comment }}字典翻译
+    dict_{{ underscore(column.java_field) }}_format(row, column) {
+      return this.selectDictLabel(this.{{ getColumnDictType(column) }}Options, row.{{ underscore(column.java_field) }});
+    },
+{%- endif %}
+{%- endfor %}
+    // 取消按钮
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    // 表单重置
+    reset() {
+      this.form = {
+{%- for column in table.columns %}
+        {{ underscore(column.java_field) }}: null{% if not loop.last %},{% endif %}
+{%- endfor %}
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+{%- for column in table.columns %}
+{%- if column.html_type == 'datetime' %}
+      this.daterange{{ column.java_field }} = [];
+{%- endif %}
+{%- endfor %}
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.{{ underscore(table.pk_column.java_field) if table.pk_column }})
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "添加{{ table.function_name }}";
+    },
+    /** 修改按钮操作 */
+    handleUpdate(row) {
+      this.reset();
+      const {{ underscore(table.pk_column.java_field) if table.pk_column }} = row.{{ underscore(table.pk_column.java_field) if table.pk_column }} || this.ids
+      get{{ table.class_name }}({{ underscore(table.pk_column.java_field) if table.pk_column }}).then(response => {
+        this.form = response.data;
+        this.open = true;
+        this.title = "修改{{ table.function_name }}";
+      });
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          if (this.form.{{ underscore(table.pk_column.java_field) if table.pk_column }} != null) {
+            update{{ table.class_name }}(this.form).then(response => {
+              this.$modal.msgSuccess("修改成功");
+              this.open = false;
+              this.getList();
+            });
+          } else {
+            add{{ table.class_name }}(this.form).then(response => {
+              this.$modal.msgSuccess("新增成功");
+              this.open = false;
+              this.getList();
+            });
+          }
+        }
+      });
+    },
+    /** 删除按钮操作 */
+    handleDelete(row) {
+      const {{ table.business_name }}Ids = row.{{ underscore(table.pk_column.java_field) if table.pk_column }} || this.ids;
+      this.$modal.confirm('是否确认删除{{ table.function_name }}编号为"' + {{ table.business_name }}Ids + '"的数据项?').then(function() {
+        return del{{ table.class_name }}({{ table.business_name }}Ids);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("删除成功");
+      }).catch(() => {});
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      this.download('{{ table.module_name }}/{{ table.business_name }}/export', {
+        ...this.queryParams
+      }, `{{ table.business_name }}_${new Date().getTime()}.xlsx`)
+    },
+    /** 导入按钮操作 */
+    handleImport() {
+      this.upload.title = "{{ table.function_name }}导入";
+      this.upload.open = true;
+    },
+    /** 下载模板操作 */
+    importTemplate() {
+      importTemplate().then(response => {
+        this.$download.excel(response, '{{ table.function_name }}导入模板.xlsx');
+      });
+    },
+    // 文件上传中处理
+    handleFileUploadProgress(event, file, fileList) {
+      this.upload.isUploading = true;
+    },
+    // 文件上传成功处理
+    handleFileSuccess(response, file, fileList) {
+      this.upload.open = false;
+      this.upload.isUploading = false;
+      this.$refs.upload.clearFiles();
+      this.$alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + "</div>", "导入结果", { dangerouslyUseHTMLString: true });
+      this.getList();
+    },
+    // 提交上传文件
+    submitFileForm() {
+      this.$refs.upload.submit();
+    }
+  }
+};
+</script>