SpringSunYY пре 5 месеци
родитељ
комит
dd6c5a27b6

+ 3 - 1
ruoyi-ui/src/main.js

@@ -18,7 +18,7 @@ import './assets/icons' // icon
 import './permission' // permission control
 import { getDicts } from "@/api/system/dict/data";
 import { getConfigKey } from "@/api/system/config";
-import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi";
+import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree,getFileName,getFilePath } from "@/utils/ruoyi";
 // 分页组件
 import Pagination from "@/components/Pagination";
 // 自定义表格工具组件
@@ -48,6 +48,8 @@ Vue.prototype.selectDictLabel = selectDictLabel
 Vue.prototype.selectDictLabels = selectDictLabels
 Vue.prototype.download = download
 Vue.prototype.handleTree = handleTree
+Vue.prototype.getFileName = getFileName
+Vue.prototype.getFilePath = getFilePath
 
 // 全局组件挂载
 Vue.component('DictTag', DictTag)

+ 2 - 2
ruoyi_admin/app.py

@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
 # @Author  : YY
 
 import os
@@ -31,6 +30,7 @@ def create_app():
     try:
         from ruoyi_test import init_app as test_init_app
         test_init_app(app)
+        print("Test module registered successfully")
     except ImportError:
         print("测试模块未找到或未正确配置")
 
@@ -39,4 +39,4 @@ def create_app():
 
 if __name__ == '__main__':
     app = create_app()
-    app.run(debug=True)
+    app.run(debug=True)

+ 165 - 13
ruoyi_common/utils/base.py

@@ -3,11 +3,12 @@
 
 from io import BufferedReader, BytesIO
 import io
+import json
 import os,socket,threading,re,base64,ipaddress,math,psutil
 from zipfile import is_zipfile
 import time
-from typing import Callable, List, Literal, Optional, Type, get_args, \
-    get_origin
+from typing import Any, Callable, Dict, List, Literal, Optional, Type, \
+    get_args, get_origin
 from datetime import datetime
 from openpyxl import Workbook, load_workbook
 from openpyxl.worksheet.worksheet import Worksheet
@@ -1216,7 +1217,7 @@ class ExcelUtil:
         )
         self.render_header(sheet,header_fill)
 
-    def import_file(self, file:FileStorage, sheetname:str) -> List[BaseModel]:
+    def import_file(self, file:FileStorage, sheetname:Optional[str]=None) -> List[BaseModel]:
         """
         导入数据
 
@@ -1228,7 +1229,9 @@ class ExcelUtil:
             List[BaseModel]: 导入数据模型列表
         """
         self.check_file(file)
-        buffer = io.BufferedReader(file.stream)
+        # Read the file content into BytesIO buffer which is fully compatible with openpyxl
+        file.stream.seek(0)
+        buffer = BytesIO(file.stream.read())
         data = self.read_buffer(buffer, sheetname)
         return data
 
@@ -1249,33 +1252,182 @@ class ExcelUtil:
         if file.tell() > self.max_content_length:
             raise Exception("文件大小超过限制")
 
-    def read_buffer(self, buffer:BufferedReader,sheetname:str) -> List[BaseModel]:
+    IMPORT_SAMPLE_ROW_LIMIT = 5
+
+    def read_buffer(self, buffer, sheetname:Optional[str]=None) -> List[BaseModel]:
         """
         读取文件流
 
         Args:
-            buffer(BufferedReader): 导入文件流
+            buffer: 导入文件流 (can be BufferedReader or FileStorage stream)
             sheetname(str): 工作表名
 
         Returns:
             List[BaseModel]: 导入数据模型列表
         """
+        logger = self._get_logger()
         try:
             workbook = load_workbook(buffer,read_only=True,data_only=True)
         except Exception as e:
             raise Exception("文件格式不正确")
-        if sheetname not in workbook.sheetnames:
+        worksheets = self._get_candidate_worksheets(workbook, sheetname, logger)
+        if not worksheets:
             raise NotFound(description="工作表不存在")
-        worksheet = workbook[sheetname]
-        headers = worksheet[1]
+        failures = []
+        column_meta = self._build_excel_column_meta()
+        for worksheet in worksheets:
+            data, sample_rows = self._extract_rows_from_worksheet(
+                worksheet,
+                column_meta=column_meta
+            )
+            if data is not None:
+                if logger:
+                    logger.info(
+                        "Excel导入:选择工作表",
+                        extra={"sheet": worksheet.title, "sheetnames": workbook.sheetnames}
+                    )
+                return data
+            failures.append({
+                "sheet": worksheet.title,
+                "sample_rows": sample_rows
+            })
+            if logger:
+                logger.info(
+                    "Excel导入:工作表无有效表头,继续尝试下一张",
+                    extra={
+                        "sheet": worksheet.title,
+                        "sample_rows": json.dumps(sample_rows, ensure_ascii=False)
+                    }
+                )
+        if logger:
+            logger.error(
+                "Excel导入失败:所有工作表均缺少表头",
+                extra={
+                    "sheetnames": workbook.sheetnames,
+                    "failures": json.dumps(failures, ensure_ascii=False)
+                }
+            )
+        raise Exception("工作表为空,缺少表头行")
+
+    def _get_candidate_worksheets(self, workbook, sheetname:Optional[str], logger:Optional[Logger]) -> List[Worksheet]:
+        worksheets = list(workbook.worksheets)
+        if not worksheets:
+            return []
+        ordered = []
+        matched = None
+        if sheetname:
+            normalized_target = self._normalize_sheet_name(sheetname)
+            for worksheet in worksheets:
+                if self._normalize_sheet_name(worksheet.title) == normalized_target:
+                    matched = worksheet
+                    ordered.append(worksheet)
+                    break
+            if matched is None and logger:
+                logger.warning(
+                    "Excel导入提示:未找到指定工作表,将尝试所有工作表",
+                    extra={"expected": sheetname, "available": workbook.sheetnames}
+                )
+        for worksheet in worksheets:
+            if worksheet is matched:
+                continue
+            ordered.append(worksheet)
+        if logger:
+            logger.info(
+                "Excel导入:工作表尝试顺序",
+                extra={"order": [ws.title for ws in ordered]}
+            )
+        return ordered
+
+    def _extract_rows_from_worksheet(self, worksheet:Worksheet, column_meta:Dict[str,Dict[str,Any]]):
+        header_values = None
         data = []
-        for row in worksheet.iter_rows(min_row=2):
+        sample_rows = []
+        for row_index, row in enumerate(worksheet.iter_rows(min_row=1, values_only=True), start=1):
+            row_values = list(row)
+            if len(sample_rows) < self.IMPORT_SAMPLE_ROW_LIMIT:
+                sample_rows.append({
+                    "row": row_index,
+                    "values": self._stringify_row(row_values)
+                })
+            if header_values is None:
+                if not any(cell not in (None, "") for cell in row_values):
+                    continue
+                header_values = list(row_values)
+                continue
+            if not any(cell not in (None, "") for cell in row_values):
+                continue
             row_data = {}
-            for header, cell in zip(headers,row):
-                row_data[header.value] = cell.value
+            for header, cell_value in zip(header_values, row_values):
+                row_data[header] = self._coerce_cell_value(header, cell_value, column_meta)
             new_row = self.model.rebuild_excel_schema(row_data)
             data.append(self.model(**new_row))
-        return data
+        if header_values is None:
+            return None, sample_rows
+        return data, sample_rows
+
+    def _build_excel_column_meta(self) -> Dict[str,Dict[str,Any]]:
+        if hasattr(self, "_excel_column_meta_cache"):
+            return self._excel_column_meta_cache
+        column_meta = {}
+        for field_path, access in self.model.generate_excel_schema():
+            column_meta[access.name] = {
+                "path": field_path.split("."),
+                "access": access,
+                "target_type": None
+            }
+        self._excel_column_meta_cache = column_meta
+        return column_meta
+
+    def _coerce_cell_value(self, header:str, value:Any, column_meta:Dict[str,Dict[str,Any]]):
+        meta = column_meta.get(header)
+        if not meta:
+            return value
+        target_type = meta.get("target_type")
+        if target_type is None:
+            target_type = self._resolve_field_type(meta["path"])
+            meta["target_type"] = target_type
+        if target_type is str and value not in (None, ""):
+            return str(value)
+        return value
+
+    def _resolve_field_type(self, path:List[str]):
+        current_model = self.model
+        last_index = len(path) - 1
+        for idx, attr in enumerate(path):
+            if not hasattr(current_model, "model_fields"):
+                return None
+            field_info = current_model.model_fields.get(attr)
+            if field_info is None:
+                return None
+            if idx == last_index:
+                return get_final_type(field_info.annotation)
+            next_model = get_final_model(field_info.annotation)
+            if not next_model:
+                return None
+            current_model = next_model
+
+    @staticmethod
+    def _normalize_sheet_name(name:str) -> str:
+        return (name or "").strip().lower()
+
+    @classmethod
+    def _stringify_row(cls, row:List) -> List[str]:
+        normalized = []
+        for value in row:
+            if value is None:
+                normalized.append("")
+            elif isinstance(value, (int, float, bool, str)):
+                normalized.append(str(value))
+            else:
+                normalized.append(str(value))
+        return normalized
+
+    @staticmethod
+    def _get_logger() -> Optional[Logger]:
+        try:
+            return LogUtil.logger
+        except Exception:
+            return None
 
 
 class LogUtil:

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

@@ -59,15 +59,6 @@ export function export{{ ApiName }}(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({

+ 27 - 23
ruoyi_generator/vm/py/controller.py.vm

@@ -1,11 +1,18 @@
 {%- set is_tree = table.tpl_category == 'tree' %}
-from flask import request
+from typing import List
+
+from flask_login import login_required
+from pydantic import BeforeValidator
+from typing_extensions import Annotated
+from werkzeug.datastructures import FileStorage
 
 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.serializer import BaseSerializer, JsonSerializer
 from ruoyi_common.descriptor.validator import QueryValidator, BodyValidator, PathValidator, FileDownloadValidator, FileUploadValidator
+from ruoyi_common.domain.enum import BusinessType
 from ruoyi_common.utils.base import ExcelUtil
+from ruoyi_framework.descriptor.log import Log
 from ruoyi_framework.descriptor.permission import HasPerm, PreAuthorize
 from {{ get_import_path(table.package_name, table.module_name, 'controller') }} import {{ underscore(table.class_name) }} as {{ underscore(table.class_name) }}_bp
 from {{ get_import_path(table.package_name, table.module_name, 'domain') }} import {{ table.class_name }}
@@ -52,6 +59,7 @@ def get_{{ underscore(table.business_name) }}({{ underscore(table.pk_column.java
 @gen.route('', methods=['POST'])
 @BodyValidator()
 @PreAuthorize(HasPerm('{{ table.module_name }}:{{ table.business_name }}:add'))
+@Log(title='{{ table.function_name }}管理', business_type=BusinessType.INSERT)
 @JsonSerializer()
 def add_{{ underscore(table.business_name) }}(dto: {{ table.class_name }}):
     """新增{{ table.function_name }}"""
@@ -69,6 +77,7 @@ def add_{{ underscore(table.business_name) }}(dto: {{ table.class_name }}):
 @gen.route('', methods=['PUT'])
 @BodyValidator()
 @PreAuthorize(HasPerm('{{ table.module_name }}:{{ table.business_name }}:edit'))
+@Log(title='{{ table.function_name }}管理', business_type=BusinessType.UPDATE)
 @JsonSerializer()
 def update_{{ underscore(table.business_name) }}(dto: {{ table.class_name }}):
     """修改{{ table.function_name }}"""
@@ -87,6 +96,7 @@ def update_{{ underscore(table.business_name) }}(dto: {{ table.class_name }}):
 @gen.route('/<ids>', methods=['DELETE'])
 @PathValidator()
 @PreAuthorize(HasPerm('{{ table.module_name }}:{{ table.business_name }}:remove'))
+@Log(title='{{ table.function_name }}管理', business_type=BusinessType.DELETE)
 @JsonSerializer()
 def delete_{{ underscore(table.business_name) }}(ids: str):
     """删除{{ table.function_name }}"""
@@ -103,6 +113,8 @@ def delete_{{ underscore(table.business_name) }}(ids: str):
 @gen.route('/export', methods=['POST'])
 @FileDownloadValidator()
 @PreAuthorize(HasPerm('{{ table.module_name }}:{{ table.business_name }}:export'))
+@Log(title='{{ table.function_name }}管理', business_type=BusinessType.EXPORT)
+@BaseSerializer()
 def export_{{ underscore(table.business_name) }}(dto: {{ table.class_name }}):
     """导出{{ table.function_name }}列表"""
     {{ underscore(table.class_name) }}_entity = {{ table.class_name }}()
@@ -116,33 +128,25 @@ def export_{{ underscore(table.business_name) }}(dto: {{ table.class_name }}):
     return excel_util.export_response({{ underscore(table.business_name) }}s, "{{ table.function_name }}数据")
 
 @gen.route('/importTemplate', methods=['POST'])
-@FileDownloadValidator()
-@PreAuthorize(HasPerm('{{ table.module_name }}:{{ table.business_name }}:import'))
+@login_required
+@BaseSerializer()
 def import_template():
     """下载{{ table.function_name }}导入模板"""
     excel_util = ExcelUtil({{ table.class_name }})
-    return excel_util.import_template_response("{{ table.function_name }}导入模板")
+    return excel_util.import_template_response(sheetname="{{ table.function_name }}数据")
 
 @gen.route('/importData', methods=['POST'])
 @FileUploadValidator()
 @PreAuthorize(HasPerm('{{ table.module_name }}:{{ table.business_name }}:import'))
+@Log(title='{{ table.function_name }}管理', business_type=BusinessType.IMPORT)
 @JsonSerializer()
-def import_data():
+def import_data(
+    file: List[FileStorage],
+    update_support: Annotated[bool, BeforeValidator(lambda x: x != "0")]
+):
     """导入{{ 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 }})
-        {{ underscore(table.business_name) }}_list = excel_util.import_file(file, sheetname="{{ table.function_name }}数据")
-        
-        # 调用Service层处理导入逻辑
-        msg = {{ underscore(table.class_name) }}_service.import_{{ underscore(table.class_name) }}({{ underscore(table.business_name) }}_list, update_support)
-        return AjaxResponse.from_success(msg=msg)
-    except Exception as e:
-        return AjaxResponse.from_error(msg=f'导入失败: {str(e)}')
+    file = file[0]
+    excel_util = ExcelUtil({{ table.class_name }})
+    {{ underscore(table.business_name) }}_list = excel_util.import_file(file, sheetname="{{ table.function_name }}数据")
+    msg = {{ underscore(table.class_name) }}_service.import_{{ underscore(table.class_name) }}({{ underscore(table.business_name) }}_list, update_support)
+    return AjaxResponse.from_success(msg=msg)

+ 61 - 24
ruoyi_generator/vm/py/entity.py.vm

@@ -3,42 +3,79 @@
 # @FileName: {{ table.class_name }}.py
 # @Time    : {{ datetime }}
 
+{%- set flags = namespace(has_datetime=False, needs_str_to_int=False, has_query=False) %}
+{%- for column in table.columns %}
+{%- set col_type = (column.java_type or 'String')|lower %}
+{%- if col_type in ['date', 'datetime', 'timestamp'] %}
+{%- set flags.has_datetime = True %}
+{%- endif %}
+{%- if col_type in ['integer', 'int', 'long', 'short'] %}
+{%- set flags.needs_str_to_int = True %}
+{%- endif %}
+{%- if column.is_query == '1' %}
+{%- set flags.has_query = True %}
+{%- endif %}
+{%- endfor %}
+
 from typing import Optional, Annotated
+{%- if flags.has_datetime %}
 from datetime import datetime
-from pydantic import Field, BeforeValidator
+{%- endif %}
+from pydantic import Field{% if flags.has_datetime or flags.needs_str_to_int %}, BeforeValidator{% endif %}
 from ruoyi_common.base.model import BaseEntity
+{%- if flags.has_datetime and flags.needs_str_to_int %}
+from ruoyi_common.base.transformer import to_datetime, str_to_int
+{%- elif flags.has_datetime %}
 from ruoyi_common.base.transformer import to_datetime
-from ruoyi_common.base.schema_excel import ExcelAccess
+{%- elif flags.needs_str_to_int %}
+from ruoyi_common.base.transformer import str_to_int
+{%- endif %}
+from ruoyi_common.base.schema_excel import ExcelField
+{%- if flags.has_query %}
+from ruoyi_common.base.schema_vo import VoField
+{%- endif %}
+
 
 class {{ table.class_name }}(BaseEntity):
     """
     {{ table.table_comment }}对象
     """
 {%- for column in table.columns %}
-{%- set excel_name = (column.column_comment or column.java_field)|replace('"', '\\"') %}
-{%- if column.java_type == 'String' or column.java_type == 'str' %}
-    # {{ column.column_comment }}
-    {{ underscore(column.java_field) }}: Optional[str] = Field(default=None, description="{{ column.column_comment|replace('"','\\"') }}", json_schema_extra={"excel_access": ExcelAccess(name="{{ excel_name }}")})
-{%- elif column.java_type == 'Integer' or column.java_type == 'int' %}
-    # {{ column.column_comment }}
-    {{ underscore(column.java_field) }}: Optional[int] = Field(default=None, description="{{ column.column_comment|replace('"','\\"') }}", json_schema_extra={"excel_access": ExcelAccess(name="{{ excel_name }}")})
-{%- elif column.java_type == 'Long' %}
-    # {{ column.column_comment }}
-    {{ underscore(column.java_field) }}: Optional[int] = Field(default=None, description="{{ column.column_comment|replace('"','\\"') }}", json_schema_extra={"excel_access": ExcelAccess(name="{{ excel_name }}")})
-{%- elif column.java_type == 'Float' or column.java_type == 'Double' %}
-    # {{ column.column_comment }}
-    {{ underscore(column.java_field) }}: Optional[float] = Field(default=None, description="{{ column.column_comment|replace('"','\\"') }}", json_schema_extra={"excel_access": ExcelAccess(name="{{ excel_name }}")})
-{%- elif column.java_type == 'Boolean' or column.java_type == 'bool' %}
-    # {{ column.column_comment }}
-    {{ underscore(column.java_field) }}: Optional[bool] = Field(default=None, description="{{ column.column_comment|replace('"','\\"') }}", json_schema_extra={"excel_access": ExcelAccess(name="{{ excel_name }}")})
-{%- elif column.java_type == 'Date' or column.java_type == 'DateTime' %}
-    # {{ column.column_comment }}
-    {{ underscore(column.java_field) }}: Annotated[Optional[datetime], BeforeValidator(to_datetime())] = Field(default=None, description="{{ column.column_comment|replace('"','\\"') }}", json_schema_extra={"excel_access": ExcelAccess(name="{{ excel_name }}")})
-{%- else %}
-    # {{ column.column_comment }}
-    {{ underscore(column.java_field) }}: Optional[str] = Field(default=None, description="{{ column.column_comment|replace('"','\\"') }}", json_schema_extra={"excel_access": ExcelAccess(name="{{ excel_name }}")})
+{%- set raw_comment = column.column_comment or column.java_field %}
+{%- set display_comment = raw_comment or column.java_field %}
+{%- set comment = (display_comment)|replace('"','\\"') %}
+{%- set excel_name = (raw_comment or column.java_field)|replace('"','\\"') %}
+{%- set attr_name = underscore(column.java_field) %}
+{%- set col_type = (column.java_type or 'String') %}
+{%- set col_type_lower = col_type|lower %}
+{%- set py_type = 'Optional[str]' %}
+{%- if col_type_lower in ['integer', 'int', 'long', 'short'] %}
+{%- set py_type = 'Optional[int]' %}
+{%- elif col_type_lower in ['float', 'double', 'bigdecimal', 'decimal'] %}
+{%- set py_type = 'Optional[float]' %}
+{%- elif col_type_lower in ['boolean', 'bool'] %}
+{%- set py_type = 'Optional[bool]' %}
+{%- elif col_type_lower in ['date', 'datetime', 'timestamp'] %}
+{%- set py_type = 'Optional[datetime]' %}
 {%- endif %}
+{%- set metadata = [] %}
+{%- if col_type_lower in ['integer', 'int', 'long', 'short'] %}
+{%- set _ = metadata.append('BeforeValidator(str_to_int)') %}
+{%- elif col_type_lower in ['date', 'datetime', 'timestamp'] %}
+{%- set _ = metadata.append('BeforeValidator(to_datetime())') %}
+{%- endif %}
+{%- set _ = metadata.append('Field(default=None, description="' ~ comment ~ '")') %}
+{%- if column.is_query == '1' %}
+{%- set _ = metadata.append('VoField(query=True)') %}
+{%- endif %}
+{%- set _ = metadata.append('ExcelField(name="' ~ excel_name ~ '")') %}
+    # {{ display_comment }}
+    {{ attr_name }}: Annotated[
+        {{ py_type }},
+        {{ metadata | join(',\n        ') }}
+    ]
 {%- endfor %}
+
     # 页码
     page_num: Optional[int] = Field(default=1, description="页码")
     # 每页数量

+ 37 - 39
ruoyi_generator/vm/py/service.py.vm

@@ -5,6 +5,8 @@
 
 from typing import List, Tuple
 
+from ruoyi_common.exception import ServiceException
+from ruoyi_common.utils.base import LogUtil
 from {{ get_import_path(table.package_name, table.module_name, 'domain') }} import {{ table.class_name }}
 from {{ get_import_path(table.package_name, table.module_name, 'mapper') }}.{{ underscore(table.class_name) }}_mapper import {{ underscore(table.class_name) }}_mapper
 
@@ -79,66 +81,62 @@ class {{ underscore(table.class_name) }}_service:
         return {{ underscore(table.class_name) }}_mapper.delete_{{ underscore(table.class_name) }}_by_ids(ids)
     {% endif %}
 
-    def import_{{ underscore(table.class_name) }}(self, {{ underscore(table.business_name) }}_list: List[{{ table.class_name }}], update_support: bool = False) -> str:
+    def import_{{ underscore(table.class_name) }}(self, {{ underscore(table.business_name) }}_list: List[{{ table.class_name }}], is_update: bool = False) -> str:
         """
         导入{{ table.function_name }}数据
 
         Args:
             {{ underscore(table.business_name) }}_list (List[{{ table.class_name }}]): {{ table.function_name }}列表
-            update_support (bool): 是否更新已存在的数据
+            is_update (bool): 是否更新已存在的数据
 
         Returns:
             str: 导入结果消息
         """
         if not {{ underscore(table.business_name) }}_list:
-            raise Exception("导入{{ table.function_name }}数据不能为空")
-        
+            raise ServiceException("导入{{ table.function_name }}数据不能为空")
+
         success_count = 0
-        failure_count = 0
-        failure_msg = []
-        
-        for index, {{ underscore(table.business_name) }} in enumerate({{ underscore(table.business_name) }}_list):
+        fail_count = 0
+        success_msg = ""
+        fail_msg = ""
+
+        for {{ underscore(table.business_name) }} in {{ underscore(table.business_name) }}_list:
             try:
+                display_value = {{ underscore(table.business_name) }}
                 {% if table.pk_column %}
-                # 检查是否已存在
-                existing = {{ underscore(table.class_name) }}_mapper.select_{{ underscore(table.class_name) }}_by_id({{ underscore(table.business_name) }}.{{ underscore(table.pk_column.java_field) }})
+                display_value = getattr({{ underscore(table.business_name) }}, "{{ underscore(table.pk_column.java_field) }}", display_value)
+                existing = None
+                if {{ underscore(table.business_name) }}.{{ underscore(table.pk_column.java_field) }} is not None:
+                    existing = {{ underscore(table.class_name) }}_mapper.select_{{ underscore(table.class_name) }}_by_id({{ underscore(table.business_name) }}.{{ underscore(table.pk_column.java_field) }})
                 if existing:
-                    if update_support:
-                        # 更新数据
+                    if is_update:
                         result = {{ underscore(table.class_name) }}_mapper.update_{{ underscore(table.class_name) }}({{ underscore(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}行数据已存在")
+                        fail_count += 1
+                        fail_msg += f"<br/> 第{fail_count}条数据,已存在:{display_value}"
+                        continue
                 else:
-                    # 新增数据
                     result = {{ underscore(table.class_name) }}_mapper.insert_{{ underscore(table.class_name) }}({{ underscore(table.business_name) }})
-                    if result > 0:
-                        success_count += 1
-                    else:
-                        failure_count += 1
-                        failure_msg.append(f"第{index + 2}行导入失败")
                 {% else %}
-                # 没有主键,直接插入
+                display_value = str(display_value)
                 result = {{ underscore(table.class_name) }}_mapper.insert_{{ underscore(table.class_name) }}({{ underscore(table.business_name) }})
+                {% endif %}
                 if result > 0:
                     success_count += 1
+                    success_msg += f"<br/> 第{success_count}条数据,操作成功:{display_value}"
                 else:
-                    failure_count += 1
-                    failure_msg.append(f"第{index + 2}行导入失败")
-                {% endif %}
+                    fail_count += 1
+                    fail_msg += f"<br/> 第{fail_count}条数据,操作失败:{display_value}"
             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)
+                fail_count += 1
+                fail_msg += f"<br/> 第{fail_count}条数据,导入失败,原因:{e.__class__.__name__}"
+                LogUtil.logger.error(f"导入{{ table.function_name }}失败,原因:{e}")
+
+        if fail_count > 0:
+            if success_msg:
+                fail_msg = f"导入成功{success_count}条,失败{fail_count}条。{success_msg}<br/>" + fail_msg
+            else:
+                fail_msg = f"导入成功{success_count}条,失败{fail_count}条。{fail_msg}"
+            raise ServiceException(fail_msg)
+        success_msg = f"恭喜您,数据已全部导入成功!共 {success_count} 条,数据如下:" + success_msg
+        return success_msg

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

@@ -331,7 +331,6 @@ import "@riophae/vue-treeselect/dist/vue-treeselect.css";
 {%- set tree_code_field = to_camel_case(raw_tree_code, False) %}
 {%- set tree_parent_field = to_camel_case(raw_tree_parent, False) %}
 {%- set tree_name_field = to_camel_case(raw_tree_name, False) %}
-import { getFileName, getFilePath } from '@/utils/ruoyi'
 
 export default {
   name: "{{ table.class_name }}",

+ 7 - 6
ruoyi_generator/vm/vue/index.vue.vm

@@ -283,7 +283,7 @@
         :limit="1"
         accept=".xlsx, .xls"
         :headers="upload.headers"
-        :action="upload.url"
+        :action="upload.url + '?updateSupport=' + upload.updateSupport"
         :disabled="upload.isUploading"
         :on-progress="handleFileUploadProgress"
         :on-success="handleFileSuccess"
@@ -311,9 +311,8 @@
 <script>
 {% set apiName = to_camel_case(table.class_name, False) %}
 {% set ApiName = capitalize_first(apiName) %}
-import { list{{ ApiName }}, get{{ ApiName }}, del{{ ApiName }}, add{{ ApiName }}, update{{ ApiName }}, export{{ ApiName }}, importTemplate, importData } from "@/api/{{ table.module_name }}/{{ table.business_name }}";
+import { list{{ ApiName }}, get{{ ApiName }}, del{{ ApiName }}, add{{ ApiName }}, update{{ ApiName }}, export{{ ApiName }}, importData } from "@/api/{{ table.module_name }}/{{ table.business_name }}";
 import { getToken } from "@/utils/auth";
-import { getFileName, getFilePath } from '@/utils/ruoyi'
 
 export default {
   name: "{{ table.class_name }}",
@@ -550,9 +549,11 @@ export default {
     },
     /** 下载模板操作 */
     importTemplate() {
-      importTemplate().then(response => {
-        this.$download.excel(response, '{{ table.function_name }}导入模板.xlsx');
-      });
+      this.download(
+        "{{ table.module_name }}/{{ table.business_name }}/importTemplate",
+        {},
+        "{{ table.business_name }}_template_" + new Date().getTime() + ".xlsx"
+      );
     },
     // 文件上传中处理
     handleFileUploadProgress(event, file, fileList) {