Преглед на файлове

feat(dictionary): 实现字典库管理功能

- 新增字典库树形结构查询接口
- 实现字典库新增、修改接口,支持一至三级分类
- 添加批量导入字典库功能,支持Excel文件导入
- 实现导入模板下载功能
- 完善字典库排序逻辑及父子节点关联处理
- 添加导入数据校验及错误信息提示
- 支持分类节点显示/隐藏状态同步更新
- 实现Excel导入导出对象及字段映射处理
fcw преди 4 дни
родител
ревизия
02f71b09eb
променени са 14 файла, в които са добавени 4511 реда и са изтрити 0 реда
  1. 43 0
      alien-entity/src/main/java/shop/alien/entity/store/excelVo/DictionaryLibraryExcelVo.java
  2. 167 0
      alien-store/src/main/java/shop/alien/store/controller/DictOpinionFeedbackController.java
  3. 167 0
      alien-store/src/main/java/shop/alien/store/controller/DictStoreTagController.java
  4. 157 0
      alien-store/src/main/java/shop/alien/store/controller/DictionaryLibraryController.java
  5. 158 0
      alien-store/src/main/java/shop/alien/store/controller/FilterConditionController.java
  6. 55 0
      alien-store/src/main/java/shop/alien/store/service/DictOpinionFeedbackService.java
  7. 55 0
      alien-store/src/main/java/shop/alien/store/service/DictStoreTagService.java
  8. 57 0
      alien-store/src/main/java/shop/alien/store/service/DictionaryLibraryService.java
  9. 58 0
      alien-store/src/main/java/shop/alien/store/service/FilterConditionService.java
  10. 826 0
      alien-store/src/main/java/shop/alien/store/service/impl/DictOpinionFeedbackServiceImpl.java
  11. 833 0
      alien-store/src/main/java/shop/alien/store/service/impl/DictStoreTagServiceImpl.java
  12. 955 0
      alien-store/src/main/java/shop/alien/store/service/impl/DictionaryLibraryServiceImpl.java
  13. 980 0
      alien-store/src/main/java/shop/alien/store/service/impl/FilterConditionServiceImpl.java
  14. BIN
      alien-store/src/main/resources/templates/筛选条件导入模版.xlsx

+ 43 - 0
alien-entity/src/main/java/shop/alien/entity/store/excelVo/DictionaryLibraryExcelVo.java

@@ -0,0 +1,43 @@
+package shop.alien.entity.store.excelVo;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import shop.alien.entity.store.excelVo.util.ExcelHeader;
+
+/**
+ * 经营版块Excel导入导出对象
+ *
+ * @author ssk
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "DictionaryLibraryExcelVo对象", description = "举报Excel导入导出对象")
+public class DictionaryLibraryExcelVo {
+
+    @ExcelHeader("序号")
+    @ApiModelProperty(value = "序号")
+    private String sort;
+
+    @ExcelHeader("一级分类名称")
+    @ApiModelProperty(value = "一级分类名称(必填,如果二级和三级为空,则创建一级分类)")
+    private String firstLevelName;
+
+    @ExcelHeader("二级分类名称")
+    @ApiModelProperty(value = "二级分类名称(可选,如果三级为空,则创建二级分类)")
+    private String secondLevelName;
+
+    @ExcelHeader("三级分类名称")
+    @ApiModelProperty(value = "三级分类名称(可选,如果填写则创建三级分类)")
+    private String thirdLevelName;
+
+    @ExcelHeader("四级分类名称")
+    @ApiModelProperty(value = "四级分类名称(可选,如果填写则创建四级分类)")
+    private String fourLevelName;
+
+    @ExcelHeader("状态")
+    @ApiModelProperty(value = "状态为显示隐藏,如不填默认显示")
+    private String hidden;
+}

+ 167 - 0
alien-store/src/main/java/shop/alien/store/controller/DictOpinionFeedbackController.java

@@ -0,0 +1,167 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.store.service.DictOpinionFeedbackService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * 意见反馈字典控制器
+ */
+@Api(tags = {"平台-意见反馈字典管理"})
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/dictOpinionFeedback")
+@RequiredArgsConstructor
+public class DictOpinionFeedbackController {
+
+    private final DictOpinionFeedbackService dictOpinionFeedbackService;
+
+    @ApiOperation("查询意见反馈字典(三级树形结构)")
+    @ApiOperationSupport(order = 1)
+    @GetMapping("/queryDictOpinionFeedbackTree")
+    public R<List<StoreDictionary>> queryDictOpinionFeedbackTree() {
+        log.info("dictOpinionFeedback.queryDictOpinionFeedbackTree");
+        List<StoreDictionary> result = dictOpinionFeedbackService.queryDictOpinionFeedbackTree();
+        return R.data(result);
+    }
+
+    @ApiOperation("新增意见反馈字典(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/addDictOpinionFeedback")
+    public R<Boolean> addDictOpinionFeedback(@RequestBody StoreDictionary storeDictionary) {
+        log.info("dictOpinionFeedback.addDictOpinionFeedback:{}", storeDictionary);
+        try {
+            boolean result = dictOpinionFeedbackService.addDictOpinionFeedback(storeDictionary);
+            if (result) {
+                return R.success("新增成功");
+            } else {
+                return R.fail("新增失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictOpinionFeedback.addDictOpinionFeedback error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictOpinionFeedback.addDictOpinionFeedback error", e);
+            return R.fail("新增失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改意见反馈字典(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 3)
+    @PutMapping("/updateDictOpinionFeedback")
+    public R<Boolean> updateDictOpinionFeedback(@RequestBody StoreDictionary storeDictionary) {
+        log.info("dictOpinionFeedback.updateDictOpinionFeedback:{}", storeDictionary);
+        try {
+            boolean result = dictOpinionFeedbackService.updateDictOpinionFeedback(storeDictionary);
+            if (result) {
+                return R.success("修改成功");
+            } else {
+                return R.fail("修改失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictOpinionFeedback.updateDictOpinionFeedback error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictOpinionFeedback.updateDictOpinionFeedback error", e);
+            return R.fail("修改失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("批量导入意见反馈字典")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/import")
+    public R<String> importDictOpinionFeedback(@RequestParam("file") MultipartFile file) {
+        log.info("dictOpinionFeedback.importDictOpinionFeedback fileName={}",
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            return dictOpinionFeedbackService.importDictOpinionFeedback(file);
+        } catch (Exception e) {
+            log.error("dictOpinionFeedback.importDictOpinionFeedback error", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+//    @ApiOperation("下载意见反馈字典导入模板")
+//    @ApiOperationSupport(order = 5)
+//    @GetMapping("/downloadTemplate")
+//    public void downloadTemplate(HttpServletResponse response) throws IOException {
+//        log.info("dictOpinionFeedback.downloadTemplate");
+//        dictOpinionFeedbackService.downloadTemplate(response);
+//    }
+
+    @ApiOperation("下载意见反馈导入模板")
+    @ApiOperationSupport(order = 10)
+    @GetMapping("/downloadTemplate")
+    public void downloadTemplate(HttpServletResponse response) {
+        log.info("StoreMenuPlatformController.downloadTemplate");
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+
+        try {
+            // 从resources/templates目录读取模板文件
+            Resource resource = new ClassPathResource("templates/意见反馈导入模版.xlsx");
+            inputStream = resource.getInputStream();
+
+            // 设置响应头
+            response.reset();
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+
+            String fileName = "意见反馈导入模板.xlsx";
+            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
+            response.setHeader("Content-Disposition", "attachment;filename=\"" + encodedFileName + "\";filename*=utf-8''" + encodedFileName);
+
+            // 输出文件流
+            outputStream = response.getOutputStream();
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = inputStream.read(buffer)) > 0) {
+                outputStream.write(buffer, 0, length);
+            }
+            outputStream.flush();
+
+            log.info("意见反馈导入模板下载成功");
+        } catch (Exception e) {
+            log.error("下载意见反馈导入模板失败", e);
+            throw new RuntimeException("下载模板失败:" + e.getMessage());
+        } finally {
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (Exception e) {
+                log.error("关闭流失败", e);
+            }
+        }
+    }
+}
+
+

+ 167 - 0
alien-store/src/main/java/shop/alien/store/controller/DictStoreTagController.java

@@ -0,0 +1,167 @@
+package shop.alien.store.controller;
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.store.service.DictStoreTagService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * 店铺字典标签控制器
+ */
+@Api(tags = {"2.7平台-店铺字典标签管理"})
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/dictStoreTag")
+@RequiredArgsConstructor
+public class DictStoreTagController {
+
+    private final DictStoreTagService dictStoreTagService;
+
+    @ApiOperation("查询店铺标签字典(三级树形结构)")
+    @ApiOperationSupport(order = 1)
+    @GetMapping("/queryDictStoreTagTree")
+    public R<List<StoreDictionary>> queryDictStoreTagTree() {
+        log.info("dictStoreTag.queryDictStoreTagTree");
+        List<StoreDictionary> result = dictStoreTagService.queryDictStoreTagTree();
+        return R.data(result);
+    }
+
+    @ApiOperation("新增店铺标签字典(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/addDictStoreTag")
+    public R<Boolean> addDictStoreTag(@RequestBody StoreDictionary storeDictionary) {
+        log.info("dictStoreTag.addDictStoreTag:{}", storeDictionary);
+        try {
+            boolean result = dictStoreTagService.addDictStoreTag(storeDictionary);
+            if (result) {
+                return R.success("新增成功");
+            } else {
+                return R.fail("新增失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictStoreTag.addDictStoreTag error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictStoreTag.addDictStoreTag error", e);
+            return R.fail("新增失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改店铺标签字典(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 3)
+    @PutMapping("/updateDictStoreTag")
+    public R<Boolean> updateDictStoreTag(@RequestBody StoreDictionary storeDictionary) {
+        log.info("dictStoreTag.updateDictStoreTag:{}", storeDictionary);
+        try {
+            boolean result = dictStoreTagService.updateDictStoreTag(storeDictionary);
+            if (result) {
+                return R.success("修改成功");
+            } else {
+                return R.fail("修改失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictStoreTag.updateDictStoreTag error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictStoreTag.updateDictStoreTag error", e);
+            return R.fail("修改失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("批量导入店铺标签字典")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/import")
+    public R<String> importDictStoreTag(@RequestParam("file") MultipartFile file) {
+        log.info("dictStoreTag.importDictStoreTag fileName={}",
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            return dictStoreTagService.importDictStoreTag(file);
+        } catch (Exception e) {
+            log.error("dictStoreTag.importDictStoreTag error", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+//    @ApiOperation("下载店铺标签字典导入模板")
+//    @ApiOperationSupport(order = 5)
+//    @GetMapping("/downloadTemplate")
+//    public void downloadTemplate(HttpServletResponse response) throws IOException {
+//        log.info("dictStoreTag.downloadTemplate");
+//        dictStoreTagService.downloadTemplate(response);
+//    }
+
+    @ApiOperation("下载门店标签导入模板")
+    @ApiOperationSupport(order = 10)
+    @GetMapping("/downloadTemplate")
+    public void downloadTemplate(HttpServletResponse response) {
+        log.info("StoreMenuPlatformController.downloadTemplate");
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+
+        try {
+            // 从resources/templates目录读取模板文件
+            Resource resource = new ClassPathResource("templates/门店标签导入模版.xlsx");
+            inputStream = resource.getInputStream();
+
+            // 设置响应头
+            response.reset();
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+
+            String fileName = "门店标签导入模板.xlsx";
+            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
+            response.setHeader("Content-Disposition", "attachment;filename=\"" + encodedFileName + "\";filename*=utf-8''" + encodedFileName);
+
+            // 输出文件流
+            outputStream = response.getOutputStream();
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = inputStream.read(buffer)) > 0) {
+                outputStream.write(buffer, 0, length);
+            }
+            outputStream.flush();
+
+            log.info("门店标签导入模板下载成功");
+        } catch (Exception e) {
+            log.error("下载门店标签导入模板失败", e);
+            throw new RuntimeException("下载模板失败:" + e.getMessage());
+        } finally {
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (Exception e) {
+                log.error("关闭流失败", e);
+            }
+        }
+    }
+}
+
+

+ 157 - 0
alien-store/src/main/java/shop/alien/store/controller/DictionaryLibraryController.java

@@ -0,0 +1,157 @@
+package shop.alien.store.controller;
+
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.store.service.DictionaryLibraryService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+@Api(tags = {"2.6平台-字典库管理"})
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/dictionaryLibrary")
+@RequiredArgsConstructor
+public class DictionaryLibraryController {
+
+    private final DictionaryLibraryService dictionaryLibraryService;
+
+    @ApiOperation("查询字典库(三级树形结构)")
+    @ApiOperationSupport(order = 1)
+    @GetMapping("/queryDictionaryLibraryTree")
+    public R<List<StoreDictionary>> queryDictionaryLibraryTree() {
+        log.info("dictionaryLibrary.queryDictionaryLibraryTree");
+        List<StoreDictionary> result = dictionaryLibraryService.queryDictionaryLibraryTree();
+        return R.data(result);
+    }
+
+    @ApiOperation("新增字典库(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/addDictionaryLibrary")
+    public R<Boolean> addDictionaryLibrary(@RequestBody StoreDictionary storeDictionary) {
+        log.info("dictionaryLibrary.addDictionaryLibrary:{}", storeDictionary);
+        try {
+            boolean result = dictionaryLibraryService.addDictionaryLibrary(storeDictionary);
+            if (result) {
+                return R.success("新增成功");
+            } else {
+                return R.fail("新增失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictionaryLibrary.addDictionaryLibrary error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictionaryLibrary.addDictionaryLibrary error", e);
+            return R.fail("新增失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改字典库(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 3)
+    @PutMapping("/updateDictionaryLibrary")
+    public R<Boolean> updateDictionaryLibrary(@RequestBody StoreDictionary storeDictionary) {
+        log.info("dictionaryLibrary.updateDictionaryLibrary:{}", storeDictionary);
+        try {
+            boolean result = dictionaryLibraryService.updateDictionaryLibrary(storeDictionary);
+            if (result) {
+                return R.success("修改成功");
+            } else {
+                return R.fail("修改失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("dictionaryLibrary.updateDictionaryLibrary error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("dictionaryLibrary.updateDictionaryLibrary error", e);
+            return R.fail("修改失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("批量导入字典库")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/import")
+    public R<String> importDictionaryLibrary(@RequestParam("file") MultipartFile file) {
+        log.info("dictionaryLibrary.importDictionaryLibrary fileName={}", 
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            return dictionaryLibraryService.importDictionaryLibrary(file);
+        } catch (Exception e) {
+            log.error("dictionaryLibrary.importDictionaryLibrary error", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+//    @ApiOperation("下载字典库导入模板")
+//    @ApiOperationSupport(order = 5)
+//    @GetMapping("/downloadTemplate")
+//    public void downloadTemplate(HttpServletResponse response) throws IOException {
+//        log.info("dictionaryLibrary.downloadTemplate");
+//        dictionaryLibraryService.downloadTemplate(response);
+//    }
+
+    @ApiOperation("下载举报导入模板")
+    @ApiOperationSupport(order = 10)
+    @GetMapping("/downloadTemplate")
+    public void downloadTemplate(HttpServletResponse response) {
+        log.info("StoreMenuPlatformController.downloadTemplate");
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+
+        try {
+            // 从resources/templates目录读取模板文件
+            Resource resource = new ClassPathResource("templates/举报导入模版.xlsx");
+            inputStream = resource.getInputStream();
+
+            // 设置响应头
+            response.reset();
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+
+            String fileName = "举报导入模板.xlsx";
+            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
+            response.setHeader("Content-Disposition", "attachment;filename=\"" + encodedFileName + "\";filename*=utf-8''" + encodedFileName);
+
+            // 输出文件流
+            outputStream = response.getOutputStream();
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = inputStream.read(buffer)) > 0) {
+                outputStream.write(buffer, 0, length);
+            }
+            outputStream.flush();
+
+            log.info("举报导入模板下载成功");
+        } catch (Exception e) {
+            log.error("下载举报导入模板失败", e);
+            throw new RuntimeException("下载模板失败:" + e.getMessage());
+        } finally {
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (Exception e) {
+                log.error("关闭流失败", e);
+            }
+        }
+    }
+
+}

+ 158 - 0
alien-store/src/main/java/shop/alien/store/controller/FilterConditionController.java

@@ -0,0 +1,158 @@
+package shop.alien.store.controller;
+
+
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiOperationSupport;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.store.service.FilterConditionService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+@Api(tags = {"平台-筛选条件管理"})
+@Slf4j
+@RestController
+@CrossOrigin
+@RequestMapping("/filterCondition")
+@RequiredArgsConstructor
+public class FilterConditionController {
+
+    private final FilterConditionService filterConditionService;
+
+    @ApiOperation("查询筛选条件(三级树形结构)")
+    @ApiOperationSupport(order = 1)
+    @GetMapping("/queryFilterConditionTree")
+    public R<List<StoreDictionary>> queryFilterConditionTree() {
+        log.info("filterCondition.queryFilterConditionTree");
+        List<StoreDictionary> result = filterConditionService.queryFilterConditionTree();
+        return R.data(result);
+    }
+
+    @ApiOperation("新增筛选条件(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 2)
+    @PostMapping("/addFilterCondition")
+    public R<Boolean> addFilterCondition(@RequestBody StoreDictionary storeDictionary) {
+        log.info("storeDictionary.addFilterCondition:{}", storeDictionary);
+        try {
+            boolean result = filterConditionService.addFilterCondition(storeDictionary);
+            if (result) {
+                return R.success("新增成功");
+            } else {
+                return R.fail("新增失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("filterCondition.addFilterCondition error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("filterCondition.addFilterCondition error", e);
+            return R.fail("新增失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("修改筛选条件(支持一级、二级、三级)")
+    @ApiOperationSupport(order = 3)
+    @PutMapping("/updateFilterCondition")
+    public R<Boolean> updateFilterCondition(@RequestBody StoreDictionary storeDictionary) {
+        log.info("storeDictionary.updateFilterCondition:{}", storeDictionary);
+        try {
+            boolean result = filterConditionService.updateFilterCondition(storeDictionary);
+            if (result) {
+                return R.success("修改成功");
+            } else {
+                return R.fail("修改失败");
+            }
+        } catch (IllegalArgumentException e) {
+            log.error("storeDictionary.updateFilterCondition error: {}", e.getMessage());
+            return R.fail(e.getMessage());
+        } catch (Exception e) {
+            log.error("storeDictionary.updateFilterCondition error", e);
+            return R.fail("修改失败:" + e.getMessage());
+        }
+    }
+
+    @ApiOperation("批量导入筛选条件")
+    @ApiOperationSupport(order = 4)
+    @PostMapping("/import")
+    public R<String> importFilterCondition(@RequestParam("file") MultipartFile file) {
+        log.info("filterCondition.importFilterCondition fileName={}", 
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            return filterConditionService.importFilterCondition(file);
+        } catch (Exception e) {
+            log.error("filterCondition.importFilterCondition error", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+//    @ApiOperation("下载筛选条件导入模板")
+//    @ApiOperationSupport(order = 5)
+//    @GetMapping("/downloadTemplate")
+//    public void downloadTemplate(HttpServletResponse response) throws IOException {
+//        log.info("filterCondition.downloadTemplate");
+//        filterConditionService.downloadTemplate(response);
+//    }
+
+    @ApiOperation("下载筛选条件导入模板")
+    @ApiOperationSupport(order = 10)
+    @GetMapping("/downloadTemplate")
+    public void downloadTemplate(HttpServletResponse response) {
+        log.info("StoreMenuPlatformController.downloadTemplate");
+        InputStream inputStream = null;
+        OutputStream outputStream = null;
+
+        try {
+            // 从resources/templates目录读取模板文件
+            Resource resource = new ClassPathResource("templates/筛选条件导入模版.xlsx");
+            inputStream = resource.getInputStream();
+
+            // 设置响应头
+            response.reset();
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+
+            String fileName = "筛选条件导入模板.xlsx";
+            String encodedFileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString()).replaceAll("\\+", "%20");
+            response.setHeader("Content-Disposition", "attachment;filename=\"" + encodedFileName + "\";filename*=utf-8''" + encodedFileName);
+
+            // 输出文件流
+            outputStream = response.getOutputStream();
+            byte[] buffer = new byte[1024];
+            int length;
+            while ((length = inputStream.read(buffer)) > 0) {
+                outputStream.write(buffer, 0, length);
+            }
+            outputStream.flush();
+
+            log.info("筛选条件导入模板下载成功");
+        } catch (Exception e) {
+            log.error("下载筛选条件导入模板失败", e);
+            throw new RuntimeException("下载模板失败:" + e.getMessage());
+        } finally {
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (Exception e) {
+                log.error("关闭流失败", e);
+            }
+        }
+    }
+
+}
+

+ 55 - 0
alien-store/src/main/java/shop/alien/store/service/DictOpinionFeedbackService.java

@@ -0,0 +1,55 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 意见反馈字典管理Service
+ */
+public interface DictOpinionFeedbackService {
+
+    /**
+     * 查询意见反馈字典(三级树形结构)
+     *
+     * @return 字典树形结构列表
+     */
+    List<StoreDictionary> queryDictOpinionFeedbackTree();
+
+    /**
+     * 新增意见反馈字典(支持一级、二级、三级)
+     *
+     * @param storeDictionary 字典信息
+     * @return 新增结果
+     */
+    boolean addDictOpinionFeedback(StoreDictionary storeDictionary);
+
+    /**
+     * 修改意见反馈字典(支持一级、二级、三级)
+     *
+     * @param storeDictionary 字典信息
+     * @return 修改结果
+     */
+    boolean updateDictOpinionFeedback(StoreDictionary storeDictionary);
+
+    /**
+     * 批量导入意见反馈字典
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    R<String> importDictOpinionFeedback(org.springframework.web.multipart.MultipartFile file);
+
+    /**
+     * 下载意见反馈字典导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    void downloadTemplate(HttpServletResponse response) throws IOException;
+}
+
+

+ 55 - 0
alien-store/src/main/java/shop/alien/store/service/DictStoreTagService.java

@@ -0,0 +1,55 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 店铺字典标签管理Service
+ */
+public interface DictStoreTagService {
+
+    /**
+     * 查询店铺字典标签(三级树形结构)
+     *
+     * @return 标签树形结构列表
+     */
+    List<StoreDictionary> queryDictStoreTagTree();
+
+    /**
+     * 新增店铺字典标签(支持一级、二级、三级)
+     *
+     * @param storeDictionary 标签信息
+     * @return 新增结果
+     */
+    boolean addDictStoreTag(StoreDictionary storeDictionary);
+
+    /**
+     * 修改店铺字典标签(支持一级、二级、三级)
+     *
+     * @param storeDictionary 标签信息
+     * @return 修改结果
+     */
+    boolean updateDictStoreTag(StoreDictionary storeDictionary);
+
+    /**
+     * 批量导入店铺字典标签
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    R<String> importDictStoreTag(org.springframework.web.multipart.MultipartFile file);
+
+    /**
+     * 下载店铺字典标签导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    void downloadTemplate(HttpServletResponse response) throws IOException;
+}
+
+

+ 57 - 0
alien-store/src/main/java/shop/alien/store/service/DictionaryLibraryService.java

@@ -0,0 +1,57 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.result.R;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 平台字典库管理Service
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/01/01
+ */
+public interface DictionaryLibraryService {
+
+    /**
+     * 查询字典库(三级树形结构)
+     *
+     * @return 字典库树形结构列表
+     */
+    List<StoreDictionary> queryDictionaryLibraryTree();
+
+    /**
+     * 新增字典库(支持一级、二级、三级)
+     *
+     * @param storeDictionary 字典库信息
+     * @return 新增结果
+     */
+    boolean addDictionaryLibrary(StoreDictionary storeDictionary);
+
+    /**
+     * 修改字典库(支持一级、二级、三级)
+     *
+     * @param storeDictionary 字典库信息
+     * @return 修改结果
+     */
+    boolean updateDictionaryLibrary(StoreDictionary storeDictionary);
+
+    /**
+     * 批量导入字典库
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    R<String> importDictionaryLibrary(org.springframework.web.multipart.MultipartFile file);
+
+    /**
+     * 下载字典库导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    void downloadTemplate(HttpServletResponse response) throws IOException;
+}

+ 58 - 0
alien-store/src/main/java/shop/alien/store/service/FilterConditionService.java

@@ -0,0 +1,58 @@
+package shop.alien.store.service;
+
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * 平台筛选条件管理Service
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/01/01
+ */
+public interface FilterConditionService {
+
+    /**
+     * 查询筛选条件(三级树形结构)
+     *
+     * @return 筛选条件树形结构列表
+     */
+    List<StoreDictionary> queryFilterConditionTree();
+
+    /**
+     * 新增筛选条件(支持一级、二级、三级)
+     *
+     * @param storeDictionary 筛选条件信息
+     * @return 新增结果
+     */
+    boolean addFilterCondition(StoreDictionary storeDictionary);
+
+    /**
+     * 修改筛选条件(支持一级、二级、三级)
+     *
+     * @param storeDictionary 筛选条件信息
+     * @return 修改结果
+     */
+    boolean updateFilterCondition(StoreDictionary storeDictionary);
+
+    /**
+     * 批量导入筛选条件
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    R<String> importFilterCondition(org.springframework.web.multipart.MultipartFile file);
+
+    /**
+     * 下载筛选条件导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    void downloadTemplate(HttpServletResponse response) throws IOException;
+}
+

+ 826 - 0
alien-store/src/main/java/shop/alien/store/service/impl/DictOpinionFeedbackServiceImpl.java

@@ -0,0 +1,826 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.excelVo.DictionaryLibraryExcelVo;
+import shop.alien.entity.store.excelVo.util.ExcelHeader;
+import shop.alien.mapper.StoreDictionaryMapper;
+import shop.alien.store.service.DictOpinionFeedbackService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 意见反馈字典管理ServiceImpl
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DictOpinionFeedbackServiceImpl extends ServiceImpl<StoreDictionaryMapper, StoreDictionary> implements DictOpinionFeedbackService {
+
+    private static final String DEFAULT_TYPE_NAME = "dict_opinion_feedback";
+
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    /**
+     * 查询意见反馈字典(三级树形结构)
+     */
+    @Override
+    public List<StoreDictionary> queryDictOpinionFeedbackTree() {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.orderByAsc(StoreDictionary::getSortId);
+        List<StoreDictionary> dictList = storeDictionaryMapper.selectList(queryWrapper);
+
+        return buildTreeOptimized(dictList);
+    }
+
+    /**
+     * 构建树形结构(优化版)
+     */
+    private List<StoreDictionary> buildTreeOptimized(List<StoreDictionary> flatList) {
+        if (flatList == null || flatList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();
+        Map<Integer, List<StoreDictionary>> parentChildMap = new HashMap<>();
+        List<StoreDictionary> result = new ArrayList<>();
+
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            Integer parentId = entity.getParentId();
+
+            nodeMap.put(id, entity);
+            entity.setStoreDictionaryList(new ArrayList<>());
+
+            if (parentId == null || parentId == 0) {
+                result.add(entity);
+            } else {
+                parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(entity);
+            }
+        }
+
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            if (parentChildMap.containsKey(id)) {
+                entity.getStoreDictionaryList().addAll(parentChildMap.get(id));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 新增意见反馈字典(支持一级、二级、三级)
+     */
+    @Override
+    public boolean addDictOpinionFeedback(StoreDictionary storeDictionary) {
+        if (storeDictionary == null || StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("字典描述不能为空");
+        }
+
+        storeDictionary.setDeleteFlag(0);
+        storeDictionary.setCreatedTime(new Date());
+
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        if (parentId != 0) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+            if (parent == null || parent.getDeleteFlag() == 1) {
+                throw new IllegalArgumentException("父节点不存在或已删除");
+            }
+        }
+
+        String typeName = storeDictionary.getTypeName();
+        if (StringUtils.isBlank(typeName)) {
+            throw new IllegalArgumentException("typeName不能为空");
+        }
+
+        String maxDictId = getMaxDictIdByTypeName(typeName);
+        int nextDictId = (maxDictId == null ? 1 : Integer.parseInt(maxDictId)) + 1;
+        storeDictionary.setDictId(String.valueOf(nextDictId));
+
+        Integer maxSortId = getMaxSortIdByParentId(parentId);
+        storeDictionary.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        return this.save(storeDictionary);
+    }
+
+    /**
+     * 修改意见反馈字典(支持一级、二级、三级)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateDictOpinionFeedback(StoreDictionary storeDictionary) {
+        if (storeDictionary == null || storeDictionary.getId() == null) {
+            throw new IllegalArgumentException("ID不能为空");
+        }
+        if (StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("字典描述不能为空");
+        }
+
+        StoreDictionary existing = storeDictionaryMapper.selectById(storeDictionary.getId());
+        if (existing == null) {
+            throw new IllegalArgumentException("记录不存在");
+        }
+        if (existing.getDeleteFlag() == 1) {
+            throw new IllegalArgumentException("该记录已删除");
+        }
+
+        Integer existingParentId = existing.getParentId();
+        boolean isFirstLevel = (existingParentId == null || existingParentId == 0);
+        boolean isSecondLevel = false;
+
+        if (!isFirstLevel) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(existingParentId);
+            if (parent != null) {
+                Integer grandParentId = parent.getParentId();
+                isSecondLevel = (grandParentId == null || grandParentId == 0);
+            }
+        }
+
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        Integer normalizedParentId = parentId == null ? 0 : parentId;
+        Integer normalizedExistingParentId = existingParentId == null ? 0 : existingParentId;
+        Integer oldSortId = existing.getSortId();
+        Integer newSortId = storeDictionary.getSortId();
+        boolean parentIdChanged = !normalizedParentId.equals(normalizedExistingParentId);
+
+        // 判断是否修改了关键字段(parent_id)
+        boolean keyFieldChanged = parentIdChanged;
+
+        if ((isFirstLevel || isSecondLevel) && keyFieldChanged) {
+            boolean hasChildren = hasUndeletedChildren(existing.getId());
+            if (hasChildren) {
+                throw new IllegalArgumentException("该节点存在未删除的子节点,不允许修改关键字段(父节点)");
+            }
+        }
+
+        if (parentIdChanged) {
+            if (parentId != 0) {
+                StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+                if (parent == null || parent.getDeleteFlag() == 1) {
+                    throw new IllegalArgumentException("父节点不存在或已删除");
+                }
+            }
+
+            if (newSortId == null) {
+                Integer maxSortId = getMaxSortIdByParentId(parentId);
+                int nextSortId = (maxSortId == null ? 1 : maxSortId) + 1;
+                newSortId = nextSortId;
+                storeDictionary.setSortId(newSortId);
+            }
+
+            adjustSortOrderForMoveOut(existing.getId(), normalizedExistingParentId, oldSortId);
+
+            if (newSortId != null) {
+                try {
+                    int newOrder = newSortId;
+                    Integer maxSortId = getMaxSortIdByParentId(parentId);
+                    int maxOrder = (maxSortId == null ? 0 : maxSortId);
+                    if (newOrder <= maxOrder) {
+                        adjustSortOrderForMoveIn(existing.getId(), normalizedParentId, newSortId);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析新sortId: {}", newSortId, e);
+                }
+            }
+        }
+
+        Integer oldHidden = existing.getHidden();
+        Integer newHidden = storeDictionary.getHidden() == null ? oldHidden : storeDictionary.getHidden();
+        storeDictionary.setHidden(newHidden);
+        storeDictionary.setDeleteFlag(existing.getDeleteFlag());
+        storeDictionary.setCreatedTime(existing.getCreatedTime());
+        storeDictionary.setCreatedUserId(existing.getCreatedUserId());
+        storeDictionary.setUpdatedTime(new Date());
+
+        if (!parentIdChanged) {
+            if (newSortId == null) {
+                storeDictionary.setSortId(oldSortId);
+            } else if (!newSortId.equals(oldSortId)) {
+                adjustSortOrder(existing.getId(), normalizedParentId, oldSortId, newSortId);
+            }
+        }
+
+        boolean updateResult = this.updateById(storeDictionary);
+
+        boolean hiddenChanged = (oldHidden == null && newHidden != null) ||
+                (oldHidden != null && !oldHidden.equals(newHidden));
+        if (updateResult && (isFirstLevel || isSecondLevel) && hiddenChanged && newHidden != null) {
+            updateChildrenHidden(existing.getId(), newHidden);
+        }
+
+        return updateResult;
+    }
+
+    /**
+     * 判断节点是否有未删除的子节点
+     */
+    private boolean hasUndeletedChildren(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary child = storeDictionaryMapper.selectOne(queryWrapper);
+        return child != null;
+    }
+
+    /**
+     * 递归更新子节点的显示/隐藏状态
+     */
+    private void updateChildrenHidden(Integer parentId, Integer hidden) {
+        if (parentId == null || hidden == null) {
+            return;
+        }
+
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+
+        if (children == null || children.isEmpty()) {
+            return;
+        }
+
+        Date updateTime = new Date();
+        for (StoreDictionary child : children) {
+            child.setHidden(hidden);
+            child.setUpdatedTime(updateTime);
+            storeDictionaryMapper.updateById(child);
+            updateChildrenHidden(child.getId(), hidden);
+        }
+    }
+
+    /**
+     * 根据 typeName 获取最大 dict_id
+     */
+    private String getMaxDictIdByTypeName(String typeName) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, typeName);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        queryWrapper.orderByDesc(StoreDictionary::getDictId);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null ? maxDict.getDictId() : null;
+    }
+
+    /**
+     * 根据parentId获取最大的sortId
+     */
+    private Integer getMaxSortIdByParentId(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+
+        queryWrapper.orderByDesc(StoreDictionary::getSortId);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null && maxDict.getSortId() != null ? maxDict.getSortId() : null;
+    }
+
+    /**
+     * 调整排序:当记录的sortId改变时,调整同级其他记录的排序
+     */
+    private void adjustSortOrder(Integer currentId, Integer parentId, Integer oldSortId, Integer newSortId) {
+        try {
+            int oldOrder = oldSortId;
+            int newOrder = newSortId;
+
+            if (oldOrder == newOrder) {
+                return;
+            }
+
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+
+            if (parentId == null || parentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, parentId);
+            }
+
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+
+            if (newOrder < oldOrder) {
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder >= newOrder && siblingOrder < oldOrder) {
+                            sibling.setSortId(siblingOrder + 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            } else if (newOrder > oldOrder) {
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder > oldOrder && siblingOrder <= newOrder) {
+                            sibling.setSortId(siblingOrder - 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("调整排序失败,sortId格式错误: oldSortId={}, newSortId={}", oldSortId, newSortId, e);
+        } catch (Exception e) {
+            log.error("调整排序失败", e);
+        }
+    }
+
+    /**
+     * 处理从原节点移出时的排序调整
+     */
+    private void adjustSortOrderForMoveOut(Integer currentId, Integer oldParentId, Integer oldSortId) {
+        try {
+            int oldOrder = oldSortId;
+
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+
+            if (oldParentId == null || oldParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, oldParentId);
+            }
+
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder > oldOrder) {
+                        sibling.setSortId(siblingOrder - 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移出节点排序调整失败,sortId格式错误: oldSortId={}", oldSortId, e);
+        } catch (Exception e) {
+            log.error("移出节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 处理移入新节点时的排序调整
+     */
+    private void adjustSortOrderForMoveIn(Integer currentId, Integer newParentId, Integer newSortId) {
+        try {
+            int newOrder = newSortId;
+
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+
+            if (newParentId == null || newParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, newParentId);
+            }
+
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder >= newOrder) {
+                        sibling.setSortId(siblingOrder + 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getDictId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移入节点排序调整失败,sortId格式错误: newSortId={}", newSortId, e);
+        } catch (Exception e) {
+            log.error("移入节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 批量导入意见反馈字典
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> importDictOpinionFeedback(MultipartFile file) {
+        log.info("DictOpinionFeedbackServiceImpl.importDictOpinionFeedback fileName={}",
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            if (file == null || file.isEmpty()) {
+                return R.fail("文件不能为空");
+            }
+
+            String fileName = file.getOriginalFilename();
+            if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
+                return R.fail("文件格式不正确,请上传Excel文件");
+            }
+
+            List<String> errorMessages = new ArrayList<>();
+            int successCount = 0;
+            int totalCount = 0;
+
+            try (InputStream inputStream = file.getInputStream();
+                 Workbook workbook = new XSSFWorkbook(inputStream)) {
+                Sheet sheet = workbook.getSheetAt(0);
+
+                Row headerRow = sheet.getRow(5);
+                if (headerRow == null) {
+                    return R.fail("Excel文件格式不正确,缺少表头");
+                }
+
+                Map<String, Integer> headerMap = new HashMap<>();
+                Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+                for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+                    Cell cell = headerRow.getCell(i);
+                    if (cell != null) {
+                        String headerName = getCellValueAsString(cell);
+                        if (StringUtils.isNotBlank(headerName)) {
+                            headerMap.put(headerName.trim(), i);
+                        }
+                    }
+                }
+
+                for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+                    Row row = sheet.getRow(rowIndex);
+                    if (row == null) {
+                        continue;
+                    }
+
+                    boolean isEmptyRow = true;
+                    for (int i = 0; i < row.getLastCellNum(); i++) {
+                        Cell cell = row.getCell(i);
+                        if (cell != null && cell.getCellType() != CellType.BLANK) {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                isEmptyRow = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (isEmptyRow) {
+                        continue;
+                    }
+
+                    totalCount++;
+                    DictionaryLibraryExcelVo excelVo = new DictionaryLibraryExcelVo();
+
+                    for (Field field : fields) {
+                        if (!field.isAnnotationPresent(ExcelHeader.class)) {
+                            continue;
+                        }
+                        ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                        String headerName = excelHeader.value();
+                        Integer colIndex = headerMap.get(headerName);
+                        if (colIndex == null) {
+                            continue;
+                        }
+
+                        Cell cell = row.getCell(colIndex);
+                        if (cell == null) {
+                            continue;
+                        }
+
+                        field.setAccessible(true);
+                        try {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                field.set(excelVo, cellValue.trim());
+                            }
+                        } catch (Exception e) {
+                            log.warn("读取字段{}失败:{}", headerName, e.getMessage());
+                        }
+                    }
+
+                    try {
+                        processImportData(excelVo, rowIndex + 1, errorMessages);
+                        successCount++;
+                    } catch (Exception e) {
+                        errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
+                        log.error("导入第{}行数据失败", rowIndex + 1, e);
+                    }
+                }
+            }
+
+            StringBuilder message = new StringBuilder();
+            message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
+            if (!errorMessages.isEmpty()) {
+                message.append("\n失败详情:\n");
+                int maxErrors = Math.min(errorMessages.size(), 10);
+                for (int i = 0; i < maxErrors; i++) {
+                    message.append(errorMessages.get(i)).append("\n");
+                }
+                if (errorMessages.size() > 10) {
+                    message.append(String.format("...还有%d条错误未显示", errorMessages.size() - 10));
+                }
+            }
+
+            if (successCount == 0) {
+                return R.fail(message.toString());
+            } else {
+                return R.success(message.toString());
+            }
+        } catch (Exception e) {
+            log.error("导入意见反馈字典失败", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理导入数据
+     */
+    private void processImportData(DictionaryLibraryExcelVo excelVo, int rowIndex, List<String> errorMessages) {
+        if (StringUtils.isBlank(excelVo.getFirstLevelName())) {
+            throw new IllegalArgumentException("一级分类名称不能为空");
+        }
+
+        String firstLevelName = excelVo.getFirstLevelName().trim();
+        String secondLevelName = StringUtils.isNotBlank(excelVo.getSecondLevelName())
+                ? excelVo.getSecondLevelName().trim() : null;
+        String thirdLevelName = StringUtils.isNotBlank(excelVo.getThirdLevelName())
+                ? excelVo.getThirdLevelName().trim() : null;
+
+        if (StringUtils.isNotBlank(thirdLevelName)) {
+            createOrUpdateLevel(firstLevelName, secondLevelName, thirdLevelName, 3, excelVo.getHidden(), errorMessages, rowIndex);
+        } else if (StringUtils.isNotBlank(secondLevelName)) {
+            createOrUpdateLevel(firstLevelName, secondLevelName, null, 2, excelVo.getHidden(), errorMessages, rowIndex);
+        } else {
+            createOrUpdateLevel(firstLevelName, null, null, 1, excelVo.getHidden(), errorMessages, rowIndex);
+        }
+    }
+
+    /**
+     * 创建或更新分类级别
+     */
+    private void createOrUpdateLevel(String firstLevelName, String secondLevelName, String thirdLevelName,
+                                     int level, String hidden, List<String> errorMessages, int rowIndex) {
+        try {
+            StoreDictionary firstLevel = findOrCreateLevel(firstLevelName, null, 0, hidden);
+            if (firstLevel == null) {
+                throw new IllegalArgumentException("创建一级分类失败");
+            }
+            if (level >= 2 && StringUtils.isNotBlank(secondLevelName)) {
+                StoreDictionary secondLevel = findOrCreateLevel(secondLevelName, firstLevel.getId(), 1, hidden);
+                if (secondLevel == null) {
+                    throw new IllegalArgumentException("创建二级分类失败");
+                }
+
+                if (level >= 3 && StringUtils.isNotBlank(thirdLevelName)) {
+                    StoreDictionary thirdLevel = findOrCreateLevel(thirdLevelName, secondLevel.getId(), 2, hidden);
+                    if (thirdLevel == null) {
+                        throw new IllegalArgumentException("创建三级分类失败");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    /**
+     * 查找或创建分类
+     */
+    private StoreDictionary findOrCreateLevel(String dictDetail, Integer parentId, int expectedLevel, String hidden) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "opinion_feedback", "opinion_feedback_type", "opinion_feedback_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getDictDetail, dictDetail);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+
+        StoreDictionary existing = storeDictionaryMapper.selectOne(queryWrapper);
+
+        if (existing != null) {
+            return existing;
+        }
+
+        StoreDictionary newDict = new StoreDictionary();
+        newDict.setDictDetail(dictDetail);
+        // 一级为筛选条件,二级为筛选条件种类,三级为分类,四级为时间范围
+        if (expectedLevel == 0) {
+            newDict.setTypeDetail("意见反馈");
+            newDict.setTypeName("opinion_feedback");
+        } else if (expectedLevel == 1) {
+            newDict.setTypeDetail("意见反馈种类");
+            newDict.setTypeName("opinion_feedback_type");
+        } else if (expectedLevel == 2) {
+            newDict.setTypeDetail("意见反馈分类");
+            newDict.setTypeName("opinion_feedback_classify");
+        }
+        newDict.setParentId(parentId == null ? null : parentId);
+        newDict.setHidden(StringUtils.isNotBlank(hidden) && hidden.equals("隐藏") ? 1 : 0);
+        newDict.setDeleteFlag(0);
+        newDict.setCreatedTime(new Date());
+
+        String maxDictId = getMaxDictIdByTypeName(newDict.getTypeName());
+        newDict.setDictId(String.valueOf(maxDictId == null ? 1 : Integer.parseInt(maxDictId) + 1));
+
+        Integer maxSortId = getMaxSortIdByParentId(newDict.getParentId());
+        newDict.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        boolean saved = this.save(newDict);
+        return saved ? newDict : null;
+    }
+
+    private String buildTypeDetail(int expectedLevel) {
+        if (expectedLevel == 0) {
+            return "意见反馈一级";
+        } else if (expectedLevel == 1) {
+            return "意见反馈二级";
+        } else if (expectedLevel == 2) {
+            return "意见反馈三级";
+        }
+        return "意见反馈";
+    }
+
+    /**
+     * 获取单元格值(字符串格式)
+     */
+    private String getCellValueAsString(Cell cell) {
+        if (cell == null) {
+            return null;
+        }
+
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    return cell.getDateCellValue().toString();
+                } else {
+                    double numericValue = cell.getNumericCellValue();
+                    if (numericValue == (long) numericValue) {
+                        return String.valueOf((long) numericValue);
+                    } else {
+                        return String.valueOf(numericValue);
+                    }
+                }
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                return cell.getCellFormula();
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * 下载意见反馈字典导入模板
+     */
+    @Override
+    public void downloadTemplate(HttpServletResponse response) throws IOException {
+        log.info("DictOpinionFeedbackServiceImpl.downloadTemplate");
+
+        XSSFWorkbook workbook = new XSSFWorkbook();
+        try {
+            Sheet sheet = workbook.createSheet("意见反馈字典导入模板");
+
+            Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+            List<Field> excelFields = new ArrayList<>();
+            for (Field field : fields) {
+                if (field.isAnnotationPresent(ExcelHeader.class)) {
+                    excelFields.add(field);
+                }
+            }
+
+            Font headerFont = workbook.createFont();
+            headerFont.setBold(true);
+            headerFont.setFontHeightInPoints((short) 12);
+            CellStyle headerCellStyle = workbook.createCellStyle();
+            headerCellStyle.setFont(headerFont);
+            headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
+            headerCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+            headerCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+            headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            headerCellStyle.setBorderBottom(BorderStyle.THIN);
+            headerCellStyle.setBorderTop(BorderStyle.THIN);
+            headerCellStyle.setBorderLeft(BorderStyle.THIN);
+            headerCellStyle.setBorderRight(BorderStyle.THIN);
+
+            Row headerRow = sheet.createRow(0);
+            for (int i = 0; i < excelFields.size(); i++) {
+                Field field = excelFields.get(i);
+                ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                Cell cell = headerRow.createCell(i);
+                cell.setCellValue(excelHeader.value());
+                cell.setCellStyle(headerCellStyle);
+            }
+
+            sheet.setColumnWidth(0, 25 * 256);
+            sheet.setColumnWidth(1, 25 * 256);
+            sheet.setColumnWidth(2, 25 * 256);
+            sheet.setColumnWidth(3, 15 * 256);
+
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            String fileName = URLEncoder.encode("意见反馈字典导入模板", "UTF-8").replaceAll("\\+", "%20");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+
+            OutputStream outputStream = response.getOutputStream();
+            workbook.write(outputStream);
+            outputStream.flush();
+        } finally {
+            if (workbook != null) {
+                try {
+                    workbook.close();
+                } catch (IOException e) {
+                    log.error("关闭workbook失败", e);
+                }
+            }
+        }
+    }
+}
+
+

+ 833 - 0
alien-store/src/main/java/shop/alien/store/service/impl/DictStoreTagServiceImpl.java

@@ -0,0 +1,833 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.BorderStyle;
+import org.apache.poi.ss.usermodel.Cell;
+import org.apache.poi.ss.usermodel.CellStyle;
+import org.apache.poi.ss.usermodel.CellType;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.ss.usermodel.FillPatternType;
+import org.apache.poi.ss.usermodel.Font;
+import org.apache.poi.ss.usermodel.HorizontalAlignment;
+import org.apache.poi.ss.usermodel.IndexedColors;
+import org.apache.poi.ss.usermodel.Row;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.VerticalAlignment;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.excelVo.DictionaryLibraryExcelVo;
+import shop.alien.entity.store.excelVo.util.ExcelHeader;
+import shop.alien.mapper.StoreDictionaryMapper;
+import shop.alien.store.service.DictStoreTagService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 店铺字典标签管理ServiceImpl
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DictStoreTagServiceImpl extends ServiceImpl<StoreDictionaryMapper, StoreDictionary> implements DictStoreTagService {
+
+    private static final String DEFAULT_TYPE_NAME = "dict_store_tag";
+
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    /**
+     * 查询店铺字典标签(三级树形结构)
+     *
+     * @return 标签树形结构列表
+     */
+    @Override
+    public List<StoreDictionary> queryDictStoreTagTree() {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.orderByAsc(StoreDictionary::getSortId);
+        List<StoreDictionary> dictStoreTags = storeDictionaryMapper.selectList(queryWrapper);
+
+        return buildTreeOptimized(dictStoreTags);
+    }
+
+    /**
+     * 构建树形结构
+     */
+    private List<StoreDictionary> buildTreeOptimized(List<StoreDictionary> flatList) {
+        if (flatList == null || flatList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();
+        Map<Integer, List<StoreDictionary>> parentChildMap = new HashMap<>();  // 父ID到子节点列表的映射
+        List<StoreDictionary> result = new ArrayList<>();  // 结果列表
+
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            Integer parentId = entity.getParentId();
+
+            // 存入节点映射
+            nodeMap.put(id, entity);
+
+            // 初始化子节点列表
+            entity.setStoreDictionaryList(new ArrayList<>());
+
+            // 如果是根节点(parentId为null或0),直接添加到结果
+            if (parentId == null || parentId == 0) {
+                result.add(entity);
+            } else {
+                // 否则,记录父子关系
+                parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(entity);
+            }
+        }
+
+        // 建立父子关系
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            if (parentChildMap.containsKey(id)) {
+                entity.getStoreDictionaryList().addAll(parentChildMap.get(id));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 新增店铺字典标签(支持一级、二级、三级)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean addDictStoreTag(StoreDictionary storeDictionary) {
+        if (storeDictionary == null || StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("字典描述不能为空");
+        }
+
+        storeDictionary.setDeleteFlag(0);
+        storeDictionary.setCreatedTime(new Date());
+
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        if (parentId != 0) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+            if (parent == null || parent.getDeleteFlag() == 1) {
+                throw new IllegalArgumentException("父节点不存在或已删除");
+            }
+        }
+
+        String typeName = storeDictionary.getTypeName();
+        if (StringUtils.isBlank(typeName)) {
+            throw new IllegalArgumentException("typeName不能为空");
+        }
+        String maxDictId = getMaxDictIdByTypeName(typeName);
+        int nextDictId = (maxDictId == null ? 1 : Integer.parseInt(maxDictId)) + 1;
+        storeDictionary.setDictId(String.valueOf(nextDictId));
+
+        Integer maxSortId = getMaxSortIdByParentId(parentId);
+        storeDictionary.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        return this.save(storeDictionary);
+    }
+
+    /**
+     * 修改店铺字典标签(支持一级、二级、三级)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean updateDictStoreTag(StoreDictionary storeDictionary) {
+        // 参数校验
+        if (storeDictionary == null || storeDictionary.getId() == null) {
+            throw new IllegalArgumentException("ID不能为空");
+        }
+        if (StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("字典库描述不能为空");
+        }
+
+        StoreDictionary existing = storeDictionaryMapper.selectById(storeDictionary.getId());
+        if (existing == null) {
+            throw new IllegalArgumentException("记录不存在");
+        }
+        if (existing.getDeleteFlag() == 1) {
+            throw new IllegalArgumentException("该记录已删除");
+        }
+
+        Integer existingParentId = existing.getParentId();
+        boolean isFirstLevel = (existingParentId == null || existingParentId == 0);
+        boolean isSecondLevel = false;
+
+        if (!isFirstLevel) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(existingParentId);
+            if (parent != null) {
+                Integer grandParentId = parent.getParentId();
+                isSecondLevel = (grandParentId == null || grandParentId == 0);
+            }
+        }
+
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        Integer normalizedParentId = parentId == null ? 0 : parentId;
+        Integer normalizedExistingParentId = existingParentId == null ? 0 : existingParentId;
+        Integer oldSortId = existing.getSortId();
+        Integer newSortId = storeDictionary.getSortId();
+        boolean parentIdChanged = !normalizedParentId.equals(normalizedExistingParentId);
+        boolean keyFieldChanged = parentIdChanged;
+
+        if ((isFirstLevel || isSecondLevel) && keyFieldChanged) {
+            boolean hasChildren = hasUndeletedChildren(existing.getId());
+            if (hasChildren) {
+                throw new IllegalArgumentException("该节点存在未删除的子节点,不允许修改关键字段(父节点)");
+            }
+        }
+
+        if (parentIdChanged) {
+            if (parentId != 0) {
+                StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+                if (parent == null || parent.getDeleteFlag() == 1) {
+                    throw new IllegalArgumentException("父节点不存在或已删除");
+                }
+            }
+
+            if (newSortId == null) {
+                Integer maxSortId = getMaxSortIdByParentId(parentId);
+                int nextSortId = (maxSortId == null ? 1 : maxSortId) + 1;
+                newSortId = nextSortId;
+                storeDictionary.setSortId(newSortId);
+            }
+
+            adjustSortOrderForMoveOut(existing.getId(), normalizedExistingParentId, oldSortId);
+
+            if (newSortId != null) {
+                try {
+                    int newOrder = newSortId;
+                    Integer maxSortId = getMaxSortIdByParentId(parentId);
+                    int maxOrder = (maxSortId == null ? 0 : maxSortId);
+                    if (newOrder <= maxOrder) {
+                        adjustSortOrderForMoveIn(existing.getId(), normalizedParentId, newSortId);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析新sortId: {}", newSortId, e);
+                }
+            }
+        }
+
+        Integer oldHidden = existing.getHidden();
+        Integer newHidden = storeDictionary.getHidden() == null ? oldHidden : storeDictionary.getHidden();
+        storeDictionary.setHidden(newHidden);
+        storeDictionary.setDeleteFlag(existing.getDeleteFlag());
+        storeDictionary.setCreatedTime(existing.getCreatedTime());
+        storeDictionary.setCreatedUserId(existing.getCreatedUserId());
+        storeDictionary.setUpdatedTime(new Date());
+
+        if (!parentIdChanged) {
+            if (newSortId == null) {
+                storeDictionary.setSortId(oldSortId);
+            } else if (!newSortId.equals(oldSortId)) {
+                adjustSortOrder(existing.getId(), normalizedParentId, oldSortId, newSortId);
+            }
+        }
+
+        boolean updateResult = this.updateById(storeDictionary);
+
+        boolean hiddenChanged = (oldHidden == null && newHidden != null) ||
+                (oldHidden != null && !oldHidden.equals(newHidden));
+        if (updateResult && (isFirstLevel || isSecondLevel) && hiddenChanged && newHidden != null) {
+            updateChildrenHidden(existing.getId(), newHidden);
+        }
+
+        return updateResult;
+    }
+
+    /**
+     * 判断节点是否有未删除的子节点
+     */
+    private boolean hasUndeletedChildren(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary child = storeDictionaryMapper.selectOne(queryWrapper);
+        return child != null;
+    }
+
+    /**
+     * 递归更新子节点的显示/隐藏状态
+     */
+    private void updateChildrenHidden(Integer parentId, Integer hidden) {
+        if (parentId == null || hidden == null) {
+            return;
+        }
+
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+
+        if (children == null || children.isEmpty()) {
+            return;
+        }
+
+        Date updateTime = new Date();
+        for (StoreDictionary child : children) {
+            child.setHidden(hidden);
+            child.setUpdatedTime(updateTime);
+            storeDictionaryMapper.updateById(child);
+            updateChildrenHidden(child.getId(), hidden);
+        }
+    }
+
+    /**
+     * 根据 typeName 获取最大 dict_id
+     */
+    private String getMaxDictIdByTypeName(String typeName) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, typeName);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        queryWrapper.orderByDesc(StoreDictionary::getDictId);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null ? maxDict.getDictId() : null;
+    }
+
+    /**
+     * 根据parentId获取最大的sortId
+     */
+    private Integer getMaxSortIdByParentId(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+
+        queryWrapper.orderByDesc(StoreDictionary::getSortId);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null && maxDict.getSortId() != null ? maxDict.getSortId() : null;
+    }
+
+    /**
+     * 调整排序:当记录的sortId改变时,调整同级其他记录的排序
+     */
+    private void adjustSortOrder(Integer currentId, Integer parentId, Integer oldSortId, Integer newSortId) {
+        try {
+            int oldOrder = oldSortId;
+            int newOrder = newSortId;
+
+            if (oldOrder == newOrder) {
+                return;
+            }
+
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+
+            if (parentId == null || parentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, parentId);
+            }
+
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+
+            if (newOrder < oldOrder) {
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder >= newOrder && siblingOrder < oldOrder) {
+                            sibling.setSortId(siblingOrder + 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            } else if (newOrder > oldOrder) {
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder > oldOrder && siblingOrder <= newOrder) {
+                            sibling.setSortId(siblingOrder - 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("调整排序失败,sortId格式错误: oldSortId={}, newSortId={}", oldSortId, newSortId, e);
+        } catch (Exception e) {
+            log.error("调整排序失败", e);
+        }
+    }
+
+    /**
+     * 处理从原节点移出时的排序调整
+     */
+    private void adjustSortOrderForMoveOut(Integer currentId, Integer oldParentId, Integer oldSortId) {
+        try {
+            int oldOrder = oldSortId;
+
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+
+            if (oldParentId == null || oldParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, oldParentId);
+            }
+
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder > oldOrder) {
+                        sibling.setSortId(siblingOrder - 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移出节点排序调整失败,sortId格式错误: oldSortId={}", oldSortId, e);
+        } catch (Exception e) {
+            log.error("移出节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 处理移入新节点时的排序调整
+     */
+    private void adjustSortOrderForMoveIn(Integer currentId, Integer newParentId, Integer newSortId) {
+        try {
+            int newOrder = newSortId;
+
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+
+            if (newParentId == null || newParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, newParentId);
+            }
+
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder >= newOrder) {
+                        sibling.setSortId(siblingOrder + 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getDictId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移入节点排序调整失败,sortId格式错误: newSortId={}", newSortId, e);
+        } catch (Exception e) {
+            log.error("移入节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 批量导入店铺字典标签
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> importDictStoreTag(MultipartFile file) {
+        log.info("DictStoreTagServiceImpl.importDictStoreTag fileName={}",
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            if (file == null || file.isEmpty()) {
+                return R.fail("文件不能为空");
+            }
+
+            String fileName = file.getOriginalFilename();
+            if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
+                return R.fail("文件格式不正确,请上传Excel文件");
+            }
+
+            List<String> errorMessages = new ArrayList<>();
+            int successCount = 0;
+            int totalCount = 0;
+
+            try (InputStream inputStream = file.getInputStream();
+                 Workbook workbook = new XSSFWorkbook(inputStream)) {
+                Sheet sheet = workbook.getSheetAt(0);
+
+                Row headerRow = sheet.getRow(5);
+                if (headerRow == null) {
+                    return R.fail("Excel文件格式不正确,缺少表头");
+                }
+
+                Map<String, Integer> headerMap = new HashMap<>();
+                Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+                for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+                    Cell cell = headerRow.getCell(i);
+                    if (cell != null) {
+                        String headerName = getCellValueAsString(cell);
+                        if (StringUtils.isNotBlank(headerName)) {
+                            headerMap.put(headerName.trim(), i);
+                        }
+                    }
+                }
+
+                for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+                    Row row = sheet.getRow(rowIndex);
+                    if (row == null) {
+                        continue;
+                    }
+
+                    boolean isEmptyRow = true;
+                    for (int i = 0; i < row.getLastCellNum(); i++) {
+                        Cell cell = row.getCell(i);
+                        if (cell != null && cell.getCellType() != CellType.BLANK) {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                isEmptyRow = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (isEmptyRow) {
+                        continue;
+                    }
+
+                    totalCount++;
+                    DictionaryLibraryExcelVo excelVo = new DictionaryLibraryExcelVo();
+
+                    for (Field field : fields) {
+                        if (!field.isAnnotationPresent(ExcelHeader.class)) {
+                            continue;
+                        }
+                        ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                        String headerName = excelHeader.value();
+                        Integer colIndex = headerMap.get(headerName);
+                        if (colIndex == null) {
+                            continue;
+                        }
+
+                        Cell cell = row.getCell(colIndex);
+                        if (cell == null) {
+                            continue;
+                        }
+
+                        field.setAccessible(true);
+                        try {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                field.set(excelVo, cellValue.trim());
+                            }
+                        } catch (Exception e) {
+                            log.warn("读取字段{}失败:{}", headerName, e.getMessage());
+                        }
+                    }
+
+                    try {
+                        processImportData(excelVo, rowIndex + 1, errorMessages);
+                        successCount++;
+                    } catch (Exception e) {
+                        errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
+                        log.error("导入第{}行数据失败", rowIndex + 1, e);
+                    }
+                }
+            }
+
+            StringBuilder message = new StringBuilder();
+            message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
+            if (!errorMessages.isEmpty()) {
+                message.append("\n失败详情:\n");
+                int maxErrors = Math.min(errorMessages.size(), 10);
+                for (int i = 0; i < maxErrors; i++) {
+                    message.append(errorMessages.get(i)).append("\n");
+                }
+                if (errorMessages.size() > 10) {
+                    message.append(String.format("...还有%d条错误未显示", errorMessages.size() - 10));
+                }
+            }
+
+            if (successCount == 0) {
+                return R.fail(message.toString());
+            } else {
+                return R.success(message.toString());
+            }
+        } catch (Exception e) {
+            log.error("导入店铺字典标签失败", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理导入数据
+     */
+    private void processImportData(DictionaryLibraryExcelVo excelVo, int rowIndex, List<String> errorMessages) {
+        if (StringUtils.isBlank(excelVo.getFirstLevelName())) {
+            throw new IllegalArgumentException("一级分类名称不能为空");
+        }
+
+        String firstLevelName = excelVo.getFirstLevelName().trim();
+        String secondLevelName = StringUtils.isNotBlank(excelVo.getSecondLevelName())
+                ? excelVo.getSecondLevelName().trim() : null;
+        String thirdLevelName = StringUtils.isNotBlank(excelVo.getThirdLevelName())
+                ? excelVo.getThirdLevelName().trim() : null;
+
+        if (StringUtils.isNotBlank(thirdLevelName)) {
+            createOrUpdateLevel(firstLevelName, secondLevelName, thirdLevelName, 3, excelVo.getHidden(), errorMessages, rowIndex);
+        } else if (StringUtils.isNotBlank(secondLevelName)) {
+            createOrUpdateLevel(firstLevelName, secondLevelName, null, 2, excelVo.getHidden(), errorMessages, rowIndex);
+        } else {
+            createOrUpdateLevel(firstLevelName, null, null, 1, excelVo.getHidden(), errorMessages, rowIndex);
+        }
+    }
+
+    /**
+     * 创建或更新分类级别
+     */
+    private void createOrUpdateLevel(String firstLevelName, String secondLevelName, String thirdLevelName,
+                                     int level, String hidden, List<String> errorMessages, int rowIndex) {
+        try {
+            StoreDictionary firstLevel = findOrCreateLevel(firstLevelName, null, 0, hidden);
+            if (firstLevel == null) {
+                throw new IllegalArgumentException("创建一级分类失败");
+            }
+            if (level >= 2 && StringUtils.isNotBlank(secondLevelName)) {
+                StoreDictionary secondLevel = findOrCreateLevel(secondLevelName, firstLevel.getId(), 1, hidden);
+                if (secondLevel == null) {
+                    throw new IllegalArgumentException("创建二级分类失败");
+                }
+
+                if (level >= 3 && StringUtils.isNotBlank(thirdLevelName)) {
+                    StoreDictionary thirdLevel = findOrCreateLevel(thirdLevelName, secondLevel.getId(), 2, hidden);
+                    if (thirdLevel == null) {
+                        throw new IllegalArgumentException("创建三级分类失败");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    /**
+     * 查找或创建分类
+     */
+    private StoreDictionary findOrCreateLevel(String dictDetail, Integer parentId, int expectedLevel, String hidden) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "store_tag", "store_tag_type", "store_tag_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getDictDetail, dictDetail);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+
+        StoreDictionary existing = storeDictionaryMapper.selectOne(queryWrapper);
+
+        if (existing != null) {
+            return existing;
+        }
+
+        StoreDictionary newDict = new StoreDictionary();
+        newDict.setDictDetail(dictDetail);
+        // 一级为筛选条件,二级为筛选条件种类,三级为分类,四级为时间范围
+        if (expectedLevel == 0) {
+            newDict.setTypeDetail("门店标签");
+            newDict.setTypeName("store_tag");
+        } else if (expectedLevel == 1) {
+            newDict.setTypeDetail("门店标签种类");
+            newDict.setTypeName("store_tag_type");
+        } else if (expectedLevel == 2) {
+            newDict.setTypeDetail("门店标签分类");
+            newDict.setTypeName("store_tag_classify");
+        }
+        newDict.setParentId(parentId == null ? null : parentId);
+        newDict.setHidden(StringUtils.isNotBlank(hidden) && hidden.equals("隐藏") ? 1 : 0);
+        newDict.setDeleteFlag(0);
+        newDict.setCreatedTime(new Date());
+
+        String maxDictId = getMaxDictIdByTypeName(newDict.getTypeName());
+        newDict.setDictId(String.valueOf(maxDictId == null ? 1 : Integer.parseInt(maxDictId) + 1));
+
+        Integer maxSortId = getMaxSortIdByParentId(newDict.getParentId());
+        newDict.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        boolean saved = this.save(newDict);
+        return saved ? newDict : null;
+    }
+
+    private String buildTypeDetail(int expectedLevel) {
+        if (expectedLevel == 0) {
+            return "店铺标签一级";
+        } else if (expectedLevel == 1) {
+            return "店铺标签二级";
+        } else if (expectedLevel == 2) {
+            return "店铺标签三级";
+        }
+        return "店铺标签";
+    }
+
+    /**
+     * 获取单元格值(字符串格式)
+     */
+    private String getCellValueAsString(Cell cell) {
+        if (cell == null) {
+            return null;
+        }
+
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    return cell.getDateCellValue().toString();
+                } else {
+                    double numericValue = cell.getNumericCellValue();
+                    if (numericValue == (long) numericValue) {
+                        return String.valueOf((long) numericValue);
+                    } else {
+                        return String.valueOf(numericValue);
+                    }
+                }
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                return cell.getCellFormula();
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * 下载店铺字典标签导入模板
+     */
+    @Override
+    public void downloadTemplate(HttpServletResponse response) throws IOException {
+        log.info("DictStoreTagServiceImpl.downloadTemplate");
+
+        XSSFWorkbook workbook = new XSSFWorkbook();
+        try {
+            Sheet sheet = workbook.createSheet("店铺字典标签导入模板");
+
+            Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+            List<Field> excelFields = new ArrayList<>();
+            for (Field field : fields) {
+                if (field.isAnnotationPresent(ExcelHeader.class)) {
+                    excelFields.add(field);
+                }
+            }
+
+            Font headerFont = workbook.createFont();
+            headerFont.setBold(true);
+            headerFont.setFontHeightInPoints((short) 12);
+            CellStyle headerCellStyle = workbook.createCellStyle();
+            headerCellStyle.setFont(headerFont);
+            headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
+            headerCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+            headerCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+            headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            headerCellStyle.setBorderBottom(BorderStyle.THIN);
+            headerCellStyle.setBorderTop(BorderStyle.THIN);
+            headerCellStyle.setBorderLeft(BorderStyle.THIN);
+            headerCellStyle.setBorderRight(BorderStyle.THIN);
+
+            Row headerRow = sheet.createRow(0);
+            for (int i = 0; i < excelFields.size(); i++) {
+                Field field = excelFields.get(i);
+                ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                Cell cell = headerRow.createCell(i);
+                cell.setCellValue(excelHeader.value());
+                cell.setCellStyle(headerCellStyle);
+            }
+
+            sheet.setColumnWidth(0, 25 * 256);
+            sheet.setColumnWidth(1, 25 * 256);
+            sheet.setColumnWidth(2, 25 * 256);
+            sheet.setColumnWidth(3, 15 * 256);
+
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            String fileName = URLEncoder.encode("店铺字典标签导入模板", "UTF-8").replaceAll("\\+", "%20");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+
+            OutputStream outputStream = response.getOutputStream();
+            workbook.write(outputStream);
+            outputStream.flush();
+        } finally {
+            if (workbook != null) {
+                try {
+                    workbook.close();
+                } catch (IOException e) {
+                    log.error("关闭workbook失败", e);
+                }
+            }
+        }
+    }
+}
+
+

+ 955 - 0
alien-store/src/main/java/shop/alien/store/service/impl/DictionaryLibraryServiceImpl.java

@@ -0,0 +1,955 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.excelVo.DictionaryLibraryExcelVo;
+import shop.alien.entity.store.excelVo.util.ExcelHeader;
+import shop.alien.mapper.StoreDictionaryMapper;
+import shop.alien.store.service.DictionaryLibraryService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 平台字典库管理ServiceImpl
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/01/01
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class DictionaryLibraryServiceImpl extends ServiceImpl<StoreDictionaryMapper, StoreDictionary> implements DictionaryLibraryService {
+
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    /**
+     * 查询字典库(三级树形结构)
+     *
+     * @return 字典库树形结构列表
+     */
+    @Override
+    public List<StoreDictionary> queryDictionaryLibraryTree() {
+        // 查询所有字典库数据
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.orderByAsc(StoreDictionary::getSortId);
+        List<StoreDictionary> storeDictionaryList = storeDictionaryMapper.selectList(queryWrapper);
+
+        // 构建三级树形结构
+        return buildTreeOptimized(storeDictionaryList);
+    }
+
+    /**
+     * 构建树形结构(优化版)
+     *
+     * @param flatList 扁平列表
+     * @return 树形结构列表
+     */
+    private List<StoreDictionary> buildTreeOptimized(List<StoreDictionary> flatList) {
+        if (flatList == null || flatList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 创建三个存储结构
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();  // ID到节点的映射
+        Map<Integer, List<StoreDictionary>> parentChildMap = new HashMap<>();  // 父ID到子节点列表的映射
+        List<StoreDictionary> result = new ArrayList<>();  // 结果列表
+
+        // 填充nodeMap和parentChildMap
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            Integer parentId = entity.getParentId();
+
+            // 存入节点映射
+            nodeMap.put(id, entity);
+
+            // 初始化子节点列表
+            entity.setStoreDictionaryList(new ArrayList<>());
+
+            // 如果是根节点(parentId为null或0),直接添加到结果
+            if (parentId == null || parentId == 0) {
+                result.add(entity);
+            } else {
+                // 否则,记录父子关系
+                parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(entity);
+            }
+        }
+
+        // 建立父子关系
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            if (parentChildMap.containsKey(id)) {
+                entity.getStoreDictionaryList().addAll(parentChildMap.get(id));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 新增字典库(支持一级、二级、三级)
+     *
+     * @param storeDictionary 字典库信息
+     * @return 新增结果
+     */
+    @Override
+    public boolean addDictionaryLibrary(StoreDictionary storeDictionary) {
+        // 参数校验
+        if (storeDictionary == null || StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("字典库描述不能为空");
+        }
+
+        // 设置固定字段
+//        storeDictionary.setTypeName("dictionary_library");
+//        if (StringUtils.isBlank(storeDictionary.getTypeDetail())) {
+//            storeDictionary.setTypeDetail("字典库");
+//        }
+        storeDictionary.setDeleteFlag(0);
+        storeDictionary.setCreatedTime(new Date());
+
+        // 处理 parent_id:如果为 null,设置为 0(一级)
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        // 如果是二级或三级,验证父节点是否存在
+        if (parentId != 0) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+            if (parent == null || parent.getDeleteFlag() == 1) {
+                throw new IllegalArgumentException("父节点不存在或已删除");
+            }
+        }
+
+        // 生成 dict_id:根据 typeName 查询最大 dict_id,然后 +1
+        String typeName = storeDictionary.getTypeName();
+        if (StringUtils.isBlank(typeName)) {
+            throw new IllegalArgumentException("typeName不能为空");
+        }
+        String maxDictId = getMaxDictIdByTypeName(typeName);
+        int nextDictId = (maxDictId == null ? 1 : Integer.parseInt(maxDictId)) + 1;
+        storeDictionary.setDictId(String.valueOf(nextDictId));
+
+        // 生成 sort_id
+        Integer maxSortId = getMaxSortIdByParentId(parentId);
+        storeDictionary.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        // 保存
+        return this.save(storeDictionary);
+    }
+
+    /**
+     * 修改字典库(支持一级、二级、三级)
+     *
+     * @param storeDictionary 字典库信息
+     * @return 修改结果
+     */
+    @Override
+    public boolean updateDictionaryLibrary(StoreDictionary storeDictionary) {
+        // 参数校验
+        if (storeDictionary == null || storeDictionary.getId() == null) {
+            throw new IllegalArgumentException("ID不能为空");
+        }
+        if (StringUtils.isBlank(storeDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("字典库描述不能为空");
+        }
+
+        // 查询原记录
+        StoreDictionary existing = storeDictionaryMapper.selectById(storeDictionary.getId());
+        if (existing == null) {
+            throw new IllegalArgumentException("记录不存在");
+        }
+        if (existing.getDeleteFlag() == 1) {
+            throw new IllegalArgumentException("该记录已删除");
+        }
+
+        // 判断是否是一级或二级节点
+        Integer existingParentId = existing.getParentId();
+        boolean isFirstLevel = (existingParentId == null || existingParentId == 0);
+        boolean isSecondLevel = false;
+        
+        // 如果不是一级节点,判断是否是二级节点(父节点是一级节点)
+        if (!isFirstLevel) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(existingParentId);
+            if (parent != null) {
+                Integer grandParentId = parent.getParentId();
+                isSecondLevel = (grandParentId == null || grandParentId == 0);
+            }
+        }
+
+        // 处理 parent_id:如果为 null,设置为 0(一级)
+        Integer parentId = storeDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        // 标准化parentId用于比较(null转为0)
+        Integer normalizedParentId = (parentId == null) ? 0 : parentId;
+        Integer normalizedExistingParentId = (existingParentId == null) ? 0 : existingParentId;
+        Integer oldSortId = existing.getSortId();
+        Integer newSortId = storeDictionary.getSortId();
+        boolean parentIdChanged = !normalizedParentId.equals(normalizedExistingParentId);
+        
+        // 判断是否修改了关键字段(parent_id)
+        boolean keyFieldChanged = parentIdChanged;
+        
+        // 如果是一级或二级节点,且修改了关键字段(parent_id),检查是否有未删除的子节点
+        if ((isFirstLevel || isSecondLevel) && keyFieldChanged) {
+            boolean hasChildren = hasUndeletedChildren(existing.getId());
+            if (hasChildren) {
+                throw new IllegalArgumentException("该节点存在未删除的子节点,不允许修改关键字段(父节点)");
+            }
+        }
+        
+        // 如果修改了 parentId,需要验证新的父节点是否存在
+        if (parentIdChanged) {
+            if (parentId != 0) {
+                StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+                if (parent == null || parent.getDeleteFlag() == 1) {
+                    throw new IllegalArgumentException("父节点不存在或已删除");
+                }
+            }
+            
+            // 如果用户指定了新位置(newSortId不为空),使用用户指定的位置
+            // 否则,使用新节点下的最大+1
+            if (newSortId == null) {
+                Integer maxSortId = getMaxSortIdByParentId(parentId);
+                int nextSortId = (maxSortId == null ? 1 : maxSortId) + 1;
+                newSortId = nextSortId;
+                storeDictionary.setSortId(newSortId);
+            }
+            
+            // 原节点下,原位置之后的记录需要前移(dictId - 1)
+            adjustSortOrderForMoveOut(existing.getId(), normalizedExistingParentId, oldSortId);
+            
+            // 新节点下,如果指定了新位置,需要调整新节点下的排序
+            if (newSortId != null) {
+                try {
+                    int newOrder = newSortId;
+                    Integer maxSortId = getMaxSortIdByParentId(parentId);
+                    int maxOrder = (maxSortId == null ? 0 : maxSortId);
+                    // 如果新位置不是最大+1,需要调整新节点下的排序
+                    if (newOrder <= maxOrder) {
+                        adjustSortOrderForMoveIn(existing.getId(), normalizedParentId, newSortId);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析新sortId: {}", newSortId, e);
+                }
+            }
+        }
+
+        // 设置固定字段
+//        storeDictionary.setTypeName("dictionary_library");
+        // 如果没有指定删除标记,保持原有值;如果指定了,使用新值
+        Integer oldHidden = existing.getHidden();
+        Integer newHidden = storeDictionary.getHidden() == null ? oldHidden : storeDictionary.getHidden();
+        storeDictionary.setHidden(newHidden);
+        storeDictionary.setDeleteFlag(existing.getDeleteFlag()); // 保持原有删除标记
+        storeDictionary.setCreatedTime(existing.getCreatedTime()); // 保持原有创建时间
+        storeDictionary.setCreatedUserId(existing.getCreatedUserId()); // 保持原有创建人
+        storeDictionary.setUpdatedTime(new Date()); // 更新修改时间
+
+        // 处理排序逻辑:如果 parentId 没有改变,但 dictId 改变了,需要调整同级其他记录的排序
+        if (!parentIdChanged) {
+            // 如果 dictId 为空,保持原有值
+            if (newSortId == null) {
+                storeDictionary.setSortId(oldSortId);
+            } else if (!newSortId.equals(oldSortId)) {
+                // 调整同级其他记录的排序
+                adjustSortOrder(existing.getId(), normalizedParentId, oldSortId, newSortId);
+            }
+        }
+
+        // 更新当前节点
+        boolean updateResult = this.updateById(storeDictionary);
+        
+        // 如果是一级或二级节点,且hidden值发生了变化,同步更新所有子节点的hidden值
+        boolean hiddenChanged = (oldHidden == null && newHidden != null) || 
+                                (oldHidden != null && !oldHidden.equals(newHidden));
+        if (updateResult && (isFirstLevel || isSecondLevel) && hiddenChanged && newHidden != null) {
+            updateChildrenHidden(existing.getId(), newHidden);
+        }
+        
+        return updateResult;
+    }
+
+    /**
+     * 判断节点是否有未删除的子节点
+     *
+     * @param parentId 父节点ID
+     * @return 如果有未删除的子节点返回 true,否则返回 false
+     */
+    private boolean hasUndeletedChildren(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary child = storeDictionaryMapper.selectOne(queryWrapper);
+        return child != null;
+    }
+
+    /**
+     * 递归更新子节点的显示/隐藏状态
+     * 当一级或二级节点的hidden值改变时,同步更新其所有子节点的hidden值
+     *
+     * @param parentId 父节点ID
+     * @param hidden 新的hidden值(0:不隐藏, 1:隐藏)
+     */
+    private void updateChildrenHidden(Integer parentId, Integer hidden) {
+        if (parentId == null || hidden == null) {
+            return;
+        }
+        
+        // 查询所有未删除的子节点
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (children == null || children.isEmpty()) {
+            return;
+        }
+        
+        // 批量更新子节点的hidden值
+        Date updateTime = new Date();
+        for (StoreDictionary child : children) {
+            child.setHidden(hidden);
+            child.setUpdatedTime(updateTime);
+            storeDictionaryMapper.updateById(child);
+            
+            // 递归更新子节点的子节点(二级节点的子节点是三级节点)
+            updateChildrenHidden(child.getId(), hidden);
+        }
+    }
+
+    /**
+     * 根据 typeName 获取最大 dict_id
+     *
+     * @param typeName 类型名称
+     * @return 最大 dict_id,如果不存在则返回 null
+     */
+    private String getMaxDictIdByTypeName(String typeName) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, typeName);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        queryWrapper.orderByDesc(StoreDictionary::getDictId);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null ? maxDict.getDictId() : null;
+    }
+
+    /**
+     * 根据parentId获取最大的sortId
+     *
+     * @param parentId 父节点ID
+     * @return 最大的sortId,如果不存在则返回null
+     */
+    private Integer getMaxSortIdByParentId(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+        
+        queryWrapper.orderByDesc(StoreDictionary::getSortId);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null && maxDict.getSortId() != null ? maxDict.getSortId() : null;
+    }
+
+    /**
+     * 调整排序:当记录的dictId改变时,调整同级其他记录的排序
+     * 上升则顺推:如果新dictId < 原dictId,将原dictId到新dictId之间的记录的dictId都+1
+     * 下降则顺升:如果新dictId > 原dictId,将原dictId到新dictId之间的记录的dictId都-1
+     *
+     * @param currentId 当前记录ID
+     * @param parentId 父节点ID
+     * @param oldSortId 原sortId
+     * @param newSortId 新sortId
+     */
+    private void adjustSortOrder(Integer currentId, Integer parentId, Integer oldSortId, Integer newSortId) {
+        try {
+            int oldOrder = oldSortId;
+            int newOrder = newSortId;
+            
+            // 如果排序没有改变,直接返回
+            if (oldOrder == newOrder) {
+                return;
+            }
+            
+            // 查询同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (parentId == null || parentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, parentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 上升则顺推:新dictId < 原dictId
+            if (newOrder < oldOrder) {
+                // 将新dictId到原dictId之间的记录的dictId都+1
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder >= newOrder && siblingOrder < oldOrder) {
+                            sibling.setSortId(siblingOrder + 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            } 
+            // 下降则顺升:新dictId > 原dictId
+            else if (newOrder > oldOrder) {
+                // 将原dictId到新dictId之间的记录的dictId都-1
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder > oldOrder && siblingOrder <= newOrder) {
+                            sibling.setSortId(siblingOrder - 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("调整排序失败,sortId格式错误: oldSortId={}, newSortId={}", oldSortId, newSortId, e);
+        } catch (Exception e) {
+            log.error("调整排序失败", e);
+        }
+    }
+
+    /**
+     * 处理从原节点移出时的排序调整:原位置之后的记录需要前移(dictId - 1)
+     *
+     * @param currentId 当前记录ID
+     * @param oldParentId 原父节点ID
+     * @param oldSortId 原sortId
+     */
+    private void adjustSortOrderForMoveOut(Integer currentId, Integer oldParentId, Integer oldSortId) {
+        try {
+            int oldOrder = oldSortId;
+            
+            // 查询原节点同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (oldParentId == null || oldParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, oldParentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 原位置之后的记录需要前移(dictId - 1)
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder > oldOrder) {
+                        sibling.setSortId(siblingOrder - 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移出节点排序调整失败,sortId格式错误: oldSortId={}", oldSortId, e);
+        } catch (Exception e) {
+            log.error("移出节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 处理移入新节点时的排序调整:新位置之后的记录需要后移(dictId + 1)
+     *
+     * @param currentId 当前记录ID
+     * @param newParentId 新父节点ID
+     * @param newSortId 新sortId
+     */
+    private void adjustSortOrderForMoveIn(Integer currentId, Integer newParentId, Integer newSortId) {
+        try {
+            int newOrder = newSortId;
+            
+            // 查询新节点同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (newParentId == null || newParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, newParentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 新位置之后的记录需要后移(dictId + 1)
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder >= newOrder) {
+                        sibling.setSortId(siblingOrder + 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析dictId: {}", sibling.getDictId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移入节点排序调整失败,sortId格式错误: newSortId={}", newSortId, e);
+        } catch (Exception e) {
+            log.error("移入节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 批量导入字典库
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> importDictionaryLibrary(MultipartFile file) {
+        log.info("DictionaryLibraryServiceImpl.importDictionaryLibrary fileName={}", 
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            if (file == null || file.isEmpty()) {
+                return R.fail("文件不能为空");
+            }
+
+            String fileName = file.getOriginalFilename();
+            if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
+                return R.fail("文件格式不正确,请上传Excel文件");
+            }
+
+            List<String> errorMessages = new ArrayList<>();
+            int successCount = 0;
+            int totalCount = 0;
+
+            // 使用POI读取Excel
+            try (InputStream inputStream = file.getInputStream();
+                 Workbook workbook = new XSSFWorkbook(inputStream)) {
+                Sheet sheet = workbook.getSheetAt(0);
+
+                // 获取表头
+                Row headerRow = sheet.getRow(5);
+                if (headerRow == null) {
+                    return R.fail("Excel文件格式不正确,缺少表头");
+                }
+
+                // 构建字段映射(表头名称 -> 列索引)
+                Map<String, Integer> headerMap = new HashMap<>();
+                Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+                for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+                    Cell cell = headerRow.getCell(i);
+                    if (cell != null) {
+                        String headerName = getCellValueAsString(cell);
+                        if (StringUtils.isNotBlank(headerName)) {
+                            headerMap.put(headerName.trim(), i);
+                        }
+                    }
+                }
+
+                // 读取数据行
+                for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+                    Row row = sheet.getRow(rowIndex);
+                    if (row == null) {
+                        continue;
+                    }
+
+                    // 检查是否为空行
+                    boolean isEmptyRow = true;
+                    for (int i = 0; i < row.getLastCellNum(); i++) {
+                        Cell cell = row.getCell(i);
+                        if (cell != null && cell.getCellType() != CellType.BLANK) {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                isEmptyRow = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (isEmptyRow) {
+                        continue;
+                    }
+
+                    totalCount++;
+                    DictionaryLibraryExcelVo excelVo = new DictionaryLibraryExcelVo();
+
+                    // 读取每个字段
+                    for (Field field : fields) {
+                        if (!field.isAnnotationPresent(ExcelHeader.class)) {
+                            continue;
+                        }
+                        ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                        String headerName = excelHeader.value();
+                        Integer colIndex = headerMap.get(headerName);
+                        if (colIndex == null) {
+                            continue;
+                        }
+
+                        Cell cell = row.getCell(colIndex);
+                        if (cell == null) {
+                            continue;
+                        }
+
+                        field.setAccessible(true);
+                        try {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                field.set(excelVo, cellValue.trim());
+                            }
+                        } catch (Exception e) {
+                            log.warn("读取字段{}失败:{}", headerName, e.getMessage());
+                        }
+                    }
+
+                    // 处理导入数据
+                    try {
+                        processImportData(excelVo, rowIndex + 1, errorMessages);
+                        successCount++;
+                    } catch (Exception e) {
+                        errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
+                        log.error("导入第{}行数据失败", rowIndex + 1, e);
+                    }
+                }
+            }
+
+            // 构建返回消息
+            StringBuilder message = new StringBuilder();
+            message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
+            if (!errorMessages.isEmpty()) {
+                message.append("\n失败详情:\n");
+                int maxErrors = Math.min(errorMessages.size(), 10); // 最多显示10条错误
+                for (int i = 0; i < maxErrors; i++) {
+                    message.append(errorMessages.get(i)).append("\n");
+                }
+                if (errorMessages.size() > 10) {
+                    message.append(String.format("...还有%d条错误未显示", errorMessages.size() - 10));
+                }
+            }
+
+            if (successCount == 0) {
+                return R.fail(message.toString());
+            } else if (totalCount - successCount > 0) {
+                return R.success(message.toString());
+            } else {
+                return R.success(message.toString());
+            }
+        } catch (Exception e) {
+            log.error("导入字典库失败", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 处理导入数据
+     *
+     * @param excelVo Excel数据对象
+     * @param rowIndex 行号
+     * @param errorMessages 错误消息列表
+     */
+    private void processImportData(DictionaryLibraryExcelVo excelVo, int rowIndex, List<String> errorMessages) {
+        // 校验必填字段
+        if (StringUtils.isBlank(excelVo.getFirstLevelName())) {
+            throw new IllegalArgumentException("一级分类名称不能为空");
+        }
+
+        String firstLevelName = excelVo.getFirstLevelName().trim();
+        String secondLevelName = StringUtils.isNotBlank(excelVo.getSecondLevelName()) 
+                ? excelVo.getSecondLevelName().trim() : null;
+        String thirdLevelName = StringUtils.isNotBlank(excelVo.getThirdLevelName()) 
+                ? excelVo.getThirdLevelName().trim() : null;
+
+        // 确定要创建的级别
+        if (StringUtils.isNotBlank(thirdLevelName)) {
+            // 创建三级分类
+            createOrUpdateLevel(firstLevelName, secondLevelName, thirdLevelName, 3, excelVo.getHidden(), errorMessages, rowIndex);
+        } else if (StringUtils.isNotBlank(secondLevelName)) {
+            // 创建二级分类
+            createOrUpdateLevel(firstLevelName, secondLevelName, null, 2, excelVo.getHidden(), errorMessages, rowIndex);
+        } else {
+            // 创建一级分类
+            createOrUpdateLevel(firstLevelName, null, null, 1, excelVo.getHidden(), errorMessages, rowIndex);
+        }
+    }
+
+    /**
+     * 创建或更新分类级别
+     *
+     * @param firstLevelName 一级分类名称
+     * @param secondLevelName 二级分类名称
+     * @param thirdLevelName 三级分类名称
+     * @param level 级别(1、2、3)
+     * @param hidden 状态
+     * @param errorMessages 错误消息列表
+     * @param rowIndex 行号
+     */
+    private void createOrUpdateLevel(String firstLevelName, String secondLevelName, String thirdLevelName,
+                                     int level, String hidden, List<String> errorMessages, int rowIndex) {
+        try {
+            // 查找或创建一级分类
+            StoreDictionary firstLevel = findOrCreateLevel(firstLevelName, null, 0, hidden);
+            if (firstLevel == null) {
+                throw new IllegalArgumentException("创建一级分类失败");
+            }
+            if (level >= 2 && StringUtils.isNotBlank(secondLevelName)) {
+                // 查找或创建二级分类
+                StoreDictionary secondLevel = findOrCreateLevel(secondLevelName, firstLevel.getId(), 1, hidden);
+                if (secondLevel == null) {
+                    throw new IllegalArgumentException("创建二级分类失败");
+                }
+
+                if (level >= 3 && StringUtils.isNotBlank(thirdLevelName)) {
+                    // 查找或创建三级分类
+                    StoreDictionary thirdLevel = findOrCreateLevel(thirdLevelName, secondLevel.getId(), 2, hidden);
+                    if (thirdLevel == null) {
+                        throw new IllegalArgumentException("创建三级分类失败");
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    /**
+     * 查找或创建分类
+     *
+     * @param dictDetail 分类名称
+     * @param parentId 父节点ID
+     * @param expectedLevel 期望的级别(用于校验)
+     * @param hidden 状态
+     * @return 分类对象
+     */
+    private StoreDictionary findOrCreateLevel(String dictDetail, Integer parentId, int expectedLevel, String hidden) {
+        // 查找是否存在
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "report", "report_type", "report_classify");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getDictDetail, dictDetail);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+        
+        StoreDictionary existing = storeDictionaryMapper.selectOne(queryWrapper);
+        
+        if (existing != null) {
+            return existing;
+        }
+
+        // 创建新分类
+        StoreDictionary newDict = new StoreDictionary();
+        newDict.setDictDetail(dictDetail);
+        // 一级为经营版块 ,二级为经营种类,三级为分类
+        if (expectedLevel == 0) {
+            newDict.setTypeDetail("举报");
+            newDict.setTypeName("report");
+        } else if (expectedLevel == 1) {
+            newDict.setTypeDetail("举报种类");
+            newDict.setTypeName("report_type");
+        } else if (expectedLevel == 2) {
+            newDict.setTypeDetail("分类");
+            newDict.setTypeName("report_classify");
+        }
+        newDict.setParentId(parentId == null ? null : parentId);
+        newDict.setHidden(StringUtils.isNotBlank(hidden) && hidden.equals("隐藏") ? 1 : 0);
+        newDict.setDeleteFlag(0);
+        newDict.setCreatedTime(new Date());
+
+        // 生成 dict_id
+        String maxDictId = getMaxDictIdByTypeName("dictionary_library");
+        newDict.setDictId(String.valueOf(maxDictId == null ? 1 : Integer.parseInt(maxDictId) + 1));
+        
+        // 生成 sort_id
+        Integer maxSortId = getMaxSortIdByParentId(newDict.getParentId());
+        newDict.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        boolean saved = this.save(newDict);
+        return saved ? newDict : null;
+    }
+
+    /**
+     * 获取单元格值(字符串格式)
+     *
+     * @param cell 单元格
+     * @return 字符串值
+     */
+    private String getCellValueAsString(Cell cell) {
+        if (cell == null) {
+            return null;
+        }
+        
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    return cell.getDateCellValue().toString();
+                } else {
+                    // 处理数字,避免科学计数法
+                    double numericValue = cell.getNumericCellValue();
+                    if (numericValue == (long) numericValue) {
+                        return String.valueOf((long) numericValue);
+                    } else {
+                        return String.valueOf(numericValue);
+                    }
+                }
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                return cell.getCellFormula();
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * 下载字典库导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    @Override
+    public void downloadTemplate(HttpServletResponse response) throws IOException {
+        log.info("DictionaryLibraryServiceImpl.downloadTemplate");
+        
+        XSSFWorkbook workbook = new XSSFWorkbook();
+        try {
+            Sheet sheet = workbook.createSheet("字典库导入模板");
+
+            // 获取带有 @ExcelHeader 注解的字段
+            Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+            List<Field> excelFields = new ArrayList<>();
+            for (Field field : fields) {
+                if (field.isAnnotationPresent(ExcelHeader.class)) {
+                    excelFields.add(field);
+                }
+            }
+
+            // 创建表头样式
+            Font headerFont = workbook.createFont();
+            headerFont.setBold(true);
+            headerFont.setFontHeightInPoints((short) 12);
+            CellStyle headerCellStyle = workbook.createCellStyle();
+            headerCellStyle.setFont(headerFont);
+            headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
+            headerCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+            headerCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+            headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            headerCellStyle.setBorderBottom(BorderStyle.THIN);
+            headerCellStyle.setBorderTop(BorderStyle.THIN);
+            headerCellStyle.setBorderLeft(BorderStyle.THIN);
+            headerCellStyle.setBorderRight(BorderStyle.THIN);
+
+            // 创建表头
+            Row headerRow = sheet.createRow(0);
+            for (int i = 0; i < excelFields.size(); i++) {
+                Field field = excelFields.get(i);
+                ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                Cell cell = headerRow.createCell(i);
+                cell.setCellValue(excelHeader.value());
+                cell.setCellStyle(headerCellStyle);
+            }
+
+            // 设置列宽
+            sheet.setColumnWidth(0, 25 * 256); // 一级分类名称
+            sheet.setColumnWidth(1, 25 * 256); // 二级分类名称
+            sheet.setColumnWidth(2, 25 * 256); // 三级分类名称
+            sheet.setColumnWidth(3, 15 * 256); // 状态(隐藏/显示)
+
+            // 设置响应头(必须在获取输出流之前设置)
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            String fileName = URLEncoder.encode("字典库导入模板", "UTF-8").replaceAll("\\+", "%20");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+
+            // 输出到响应流
+            OutputStream outputStream = response.getOutputStream();
+            workbook.write(outputStream);
+            outputStream.flush();
+        } finally {
+            // 确保 workbook 被正确关闭
+            if (workbook != null) {
+                try {
+                    workbook.close();
+                } catch (IOException e) {
+                    log.error("关闭workbook失败", e);
+                }
+            }
+        }
+    }
+}
+

+ 980 - 0
alien-store/src/main/java/shop/alien/store/service/impl/FilterConditionServiceImpl.java

@@ -0,0 +1,980 @@
+package shop.alien.store.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+import shop.alien.entity.result.R;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.StoreDictionary;
+import shop.alien.entity.store.excelVo.DictionaryLibraryExcelVo;
+import shop.alien.entity.store.excelVo.util.ExcelHeader;
+import shop.alien.mapper.StoreDictionaryMapper;
+import shop.alien.store.service.FilterConditionService;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 平台筛选条件管理ServiceImpl
+ *
+ * @author ssk
+ * @version 1.0
+ * @date 2025/01/01
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class FilterConditionServiceImpl extends ServiceImpl<StoreDictionaryMapper, StoreDictionary> implements FilterConditionService {
+
+    private final StoreDictionaryMapper storeDictionaryMapper;
+
+    /**
+     * 查询筛选条件(四级树形结构)
+     *
+     * @return 筛选条件树形结构列表
+     */
+    @Override
+    public List<StoreDictionary> queryFilterConditionTree() {
+        // 查询所有筛选条件数据
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.orderByAsc(StoreDictionary::getSortId);
+        List<StoreDictionary> filterConditionList = storeDictionaryMapper.selectList(queryWrapper);
+
+        // 构建四级树形结构
+        return buildTreeOptimized(filterConditionList);
+    }
+
+    /**
+     * 构建树形结构(优化版)
+     *
+     * @param flatList 扁平列表
+     * @return 树形结构列表
+     */
+    private List<StoreDictionary> buildTreeOptimized(List<StoreDictionary> flatList) {
+        if (flatList == null || flatList.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 创建三个存储结构
+        Map<Integer, StoreDictionary> nodeMap = new HashMap<>();  // ID到节点的映射
+        Map<Integer, List<StoreDictionary>> parentChildMap = new HashMap<>();  // 父ID到子节点列表的映射
+        List<StoreDictionary> result = new ArrayList<>();  // 结果列表
+
+        // 填充nodeMap和parentChildMap
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            Integer parentId = entity.getParentId();
+
+            // 存入节点映射
+            nodeMap.put(id, entity);
+
+            // 初始化子节点列表
+            entity.setStoreDictionaryList(new ArrayList<>());
+
+            // 如果是根节点(parentId为null或0),直接添加到结果
+            if (parentId == null || parentId == 0) {
+                result.add(entity);
+            } else {
+                // 否则,记录父子关系
+                parentChildMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(entity);
+            }
+        }
+
+        // 建立父子关系
+        for (StoreDictionary entity : flatList) {
+            Integer id = entity.getId();
+            if (parentChildMap.containsKey(id)) {
+                entity.getStoreDictionaryList().addAll(parentChildMap.get(id));
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * 新增筛选条件(支持一级、二级、三级、四级)
+     *
+     * @param StoreDictionary 筛选条件信息
+     * @return 新增结果
+     */
+    @Override
+    public boolean addFilterCondition(StoreDictionary StoreDictionary) {
+        // 参数校验
+        if (StoreDictionary == null || StringUtils.isBlank(StoreDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("筛选条件描述不能为空");
+        }
+
+        // 设置固定字段
+        StoreDictionary.setDeleteFlag(0);
+        StoreDictionary.setCreatedTime(new Date());
+
+        // 处理 parent_id:如果为 null,设置为 0(一级)
+        Integer parentId = StoreDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        // 如果是二级或三级,验证父节点是否存在
+        if (parentId != 0) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+            if (parent == null || parent.getDeleteFlag() == 1) {
+                throw new IllegalArgumentException("父节点不存在或已删除");
+            }
+        }
+
+        // 生成 condition_id:根据 typeName 查询最大 condition_id,然后 +1
+        String typeName = StoreDictionary.getTypeName();
+        if (StringUtils.isBlank(typeName)) {
+            throw new IllegalArgumentException("typeName不能为空");
+        }
+        String maxConditionId = getMaxConditionIdByTypeName(typeName);
+        int nextConditionId = (maxConditionId == null ? 1 : Integer.parseInt(maxConditionId)) + 1;
+        StoreDictionary.setDictId(String.valueOf(nextConditionId));
+
+        // 生成 sort_id
+        Integer maxSortId = getMaxSortIdByParentId(parentId);
+        StoreDictionary.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        // 保存
+        return this.save(StoreDictionary);
+    }
+
+    /**
+     * 修改筛选条件(支持一级、二级、三级、四级)
+     *
+     * @param StoreDictionary 筛选条件信息
+     * @return 修改结果
+     */
+    @Override
+    public boolean updateFilterCondition(StoreDictionary StoreDictionary) {
+        // 参数校验
+        if (StoreDictionary == null || StoreDictionary.getId() == null) {
+            throw new IllegalArgumentException("ID不能为空");
+        }
+        if (StringUtils.isBlank(StoreDictionary.getDictDetail())) {
+            throw new IllegalArgumentException("筛选条件描述不能为空");
+        }
+
+        // 查询原记录
+        StoreDictionary existing = storeDictionaryMapper.selectById(StoreDictionary.getId());
+        if (existing == null) {
+            throw new IllegalArgumentException("记录不存在");
+        }
+        if (existing.getDeleteFlag() == 1) {
+            throw new IllegalArgumentException("该记录已删除");
+        }
+
+        // 判断是否是一级或二级节点
+        Integer existingParentId = existing.getParentId();
+        boolean isFirstLevel = (existingParentId == null || existingParentId == 0);
+        boolean isSecondLevel = false;
+        
+        // 如果不是一级节点,判断是否是二级节点(父节点是一级节点)
+        if (!isFirstLevel) {
+            StoreDictionary parent = storeDictionaryMapper.selectById(existingParentId);
+            if (parent != null) {
+                Integer grandParentId = parent.getParentId();
+                isSecondLevel = (grandParentId == null || grandParentId == 0);
+            }
+        }
+
+        // 处理 parent_id:如果为 null,设置为 0(一级)
+        Integer parentId = StoreDictionary.getParentId();
+        if (parentId == null) {
+            parentId = 0;
+        }
+
+        // 标准化parentId用于比较(null转为0)
+        Integer normalizedParentId = (parentId == null) ? 0 : parentId;
+        Integer normalizedExistingParentId = (existingParentId == null) ? 0 : existingParentId;
+        Integer oldSortId = existing.getSortId();
+        Integer newSortId = StoreDictionary.getSortId();
+        boolean parentIdChanged = !normalizedParentId.equals(normalizedExistingParentId);
+        
+        // 判断是否修改了关键字段(parent_id)
+        boolean keyFieldChanged = parentIdChanged;
+        
+        // 如果是一级或二级节点,且修改了关键字段(parent_id),检查是否有未删除的子节点
+        if ((isFirstLevel || isSecondLevel) && keyFieldChanged) {
+            boolean hasChildren = hasUndeletedChildren(existing.getId());
+            if (hasChildren) {
+                throw new IllegalArgumentException("该节点存在未删除的子节点,不允许修改关键字段(父节点)");
+            }
+        }
+        
+        // 如果修改了 parentId,需要验证新的父节点是否存在
+        if (parentIdChanged) {
+            if (parentId != 0) {
+                StoreDictionary parent = storeDictionaryMapper.selectById(parentId);
+                if (parent == null || parent.getDeleteFlag() == 1) {
+                    throw new IllegalArgumentException("父节点不存在或已删除");
+                }
+            }
+            
+            // 如果用户指定了新位置(newSortId不为空),使用用户指定的位置
+            // 否则,使用新节点下的最大+1
+            if (newSortId == null) {
+                Integer maxSortId = getMaxSortIdByParentId(parentId);
+                int nextSortId = (maxSortId == null ? 1 : maxSortId) + 1;
+                newSortId = nextSortId;
+                StoreDictionary.setSortId(newSortId);
+            }
+            
+            // 原节点下,原位置之后的记录需要前移(sortId - 1)
+            adjustSortOrderForMoveOut(existing.getId(), normalizedExistingParentId, oldSortId);
+            
+            // 新节点下,如果指定了新位置,需要调整新节点下的排序
+            if (newSortId != null) {
+                try {
+                    int newOrder = newSortId;
+                    Integer maxSortId = getMaxSortIdByParentId(parentId);
+                    int maxOrder = (maxSortId == null ? 0 : maxSortId);
+                    // 如果新位置不是最大+1,需要调整新节点下的排序
+                    if (newOrder <= maxOrder) {
+                        adjustSortOrderForMoveIn(existing.getId(), normalizedParentId, newSortId);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析新sortId: {}", newSortId, e);
+                }
+            }
+        }
+
+        // 设置固定字段
+        // 如果没有指定删除标记,保持原有值;如果指定了,使用新值
+        Integer oldHidden = existing.getHidden();
+        Integer newHidden = StoreDictionary.getHidden() == null ? oldHidden : StoreDictionary.getHidden();
+        StoreDictionary.setHidden(newHidden);
+        StoreDictionary.setDeleteFlag(existing.getDeleteFlag()); // 保持原有删除标记
+        StoreDictionary.setCreatedTime(existing.getCreatedTime()); // 保持原有创建时间
+        StoreDictionary.setCreatedUserId(existing.getCreatedUserId()); // 保持原有创建人
+        StoreDictionary.setUpdatedTime(new Date()); // 更新修改时间
+
+        // 处理排序逻辑:如果 parentId 没有改变,但 sortId 改变了,需要调整同级其他记录的排序
+        if (!parentIdChanged) {
+            // 如果 sortId 为空,保持原有值
+            if (newSortId == null) {
+                StoreDictionary.setSortId(oldSortId);
+            } else if (!newSortId.equals(oldSortId)) {
+                // 调整同级其他记录的排序
+                adjustSortOrder(existing.getId(), normalizedParentId, oldSortId, newSortId);
+            }
+        }
+
+        // 更新当前节点
+        boolean updateResult = this.updateById(StoreDictionary);
+        
+        // 如果是一级或二级节点,且hidden值发生了变化,同步更新所有子节点的hidden值
+        boolean hiddenChanged = (oldHidden == null && newHidden != null) || 
+                                (oldHidden != null && !oldHidden.equals(newHidden));
+        if (updateResult && (isFirstLevel || isSecondLevel) && hiddenChanged && newHidden != null) {
+            updateChildrenHidden(existing.getId(), newHidden);
+        }
+        
+        return updateResult;
+    }
+
+    /**
+     * 判断节点是否有未删除的子节点
+     *
+     * @param parentId 父节点ID
+     * @return 如果有未删除的子节点返回 true,否则返回 false
+     */
+    private boolean hasUndeletedChildren(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary child = storeDictionaryMapper.selectOne(queryWrapper);
+        return child != null;
+    }
+
+    /**
+     * 递归更新子节点的显示/隐藏状态
+     * 当一级或二级节点的hidden值改变时,同步更新其所有子节点的hidden值
+     *
+     * @param parentId 父节点ID
+     * @param hidden 新的hidden值(0:不隐藏, 1:隐藏)
+     */
+    private void updateChildrenHidden(Integer parentId, Integer hidden) {
+        if (parentId == null || hidden == null) {
+            return;
+        }
+        
+        // 查询所有未删除的子节点
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+        queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        List<StoreDictionary> children = storeDictionaryMapper.selectList(queryWrapper);
+        
+        if (children == null || children.isEmpty()) {
+            return;
+        }
+        
+        // 批量更新子节点的hidden值
+        Date updateTime = new Date();
+        for (StoreDictionary child : children) {
+            child.setHidden(hidden);
+            child.setUpdatedTime(updateTime);
+            storeDictionaryMapper.updateById(child);
+            
+            // 递归更新子节点的子节点(二级节点的子节点是三级节点)
+            updateChildrenHidden(child.getId(), hidden);
+        }
+    }
+
+    /**
+     * 根据 typeName 获取最大 condition_id
+     *
+     * @param typeName 类型名称
+     * @return 最大 condition_id,如果不存在则返回 null
+     */
+    private String getMaxConditionIdByTypeName(String typeName) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, typeName);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        queryWrapper.orderByDesc(StoreDictionary::getDictId);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary maxCondition = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxCondition != null ? maxCondition.getDictId() : null;
+    }
+
+    /**
+     * 根据parentId获取最大的sortId
+     *
+     * @param parentId 父节点ID
+     * @return 最大的sortId,如果不存在则返回null
+     */
+    private Integer getMaxSortIdByParentId(Integer parentId) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+        
+        queryWrapper.orderByDesc(StoreDictionary::getSortId);
+        queryWrapper.last("LIMIT 1");
+        
+        StoreDictionary maxCondition = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxCondition != null && maxCondition.getSortId() != null ? maxCondition.getSortId() : null;
+    }
+
+    /**
+     * 调整排序:当记录的sortId改变时,调整同级其他记录的排序
+     * 上升则顺推:如果新sortId < 原sortId,将原sortId到新sortId之间的记录的sortId都+1
+     * 下降则顺升:如果新sortId > 原sortId,将原sortId到新sortId之间的记录的sortId都-1
+     *
+     * @param currentId 当前记录ID
+     * @param parentId 父节点ID
+     * @param oldSortId 原sortId
+     * @param newSortId 新sortId
+     */
+    private void adjustSortOrder(Integer currentId, Integer parentId, Integer oldSortId, Integer newSortId) {
+        try {
+            int oldOrder = oldSortId;
+            int newOrder = newSortId;
+            
+            // 如果排序没有改变,直接返回
+            if (oldOrder == newOrder) {
+                return;
+            }
+            
+            // 查询同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (parentId == null || parentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, parentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 上升则顺推:新sortId < 原sortId
+            if (newOrder < oldOrder) {
+                // 将新sortId到原sortId之间的记录的sortId都+1
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder >= newOrder && siblingOrder < oldOrder) {
+                            sibling.setSortId(siblingOrder + 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            } 
+            // 下降则顺升:新sortId > 原sortId
+            else if (newOrder > oldOrder) {
+                // 将原sortId到新sortId之间的记录的sortId都-1
+                for (StoreDictionary sibling : siblings) {
+                    try {
+                        int siblingOrder = sibling.getSortId();
+                        if (siblingOrder > oldOrder && siblingOrder <= newOrder) {
+                            sibling.setSortId(siblingOrder - 1);
+                            sibling.setUpdatedTime(new Date());
+                            storeDictionaryMapper.updateById(sibling);
+                        }
+                    } catch (NumberFormatException e) {
+                        log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                    }
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("调整排序失败,sortId格式错误: oldSortId={}, newSortId={}", oldSortId, newSortId, e);
+        } catch (Exception e) {
+            log.error("调整排序失败", e);
+        }
+    }
+
+    /**
+     * 处理从原节点移出时的排序调整:原位置之后的记录需要前移(sortId - 1)
+     *
+     * @param currentId 当前记录ID
+     * @param oldParentId 原父节点ID
+     * @param oldSortId 原sortId
+     */
+    private void adjustSortOrderForMoveOut(Integer currentId, Integer oldParentId, Integer oldSortId) {
+        try {
+            int oldOrder = oldSortId;
+            
+            // 查询原节点同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (oldParentId == null || oldParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, oldParentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 原位置之后的记录需要前移(sortId - 1)
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder > oldOrder) {
+                        sibling.setSortId(siblingOrder - 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移出节点排序调整失败,sortId格式错误: oldSortId={}", oldSortId, e);
+        } catch (Exception e) {
+            log.error("移出节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 处理移入新节点时的排序调整:新位置之后的记录需要后移(sortId + 1)
+     *
+     * @param currentId 当前记录ID
+     * @param newParentId 新父节点ID
+     * @param newSortId 新sortId
+     */
+    private void adjustSortOrderForMoveIn(Integer currentId, Integer newParentId, Integer newSortId) {
+        try {
+            int newOrder = newSortId;
+            
+            // 查询新节点同级所有记录(排除当前记录)
+            LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+            queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+            queryWrapper.ne(StoreDictionary::getId, currentId);
+            
+            if (newParentId == null || newParentId == 0) {
+                queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                        .or().eq(StoreDictionary::getParentId, 0));
+            } else {
+                queryWrapper.eq(StoreDictionary::getParentId, newParentId);
+            }
+            
+            List<StoreDictionary> siblings = storeDictionaryMapper.selectList(queryWrapper);
+            
+            if (siblings == null || siblings.isEmpty()) {
+                return;
+            }
+            
+            // 新位置之后的记录需要后移(sortId + 1)
+            for (StoreDictionary sibling : siblings) {
+                try {
+                    int siblingOrder = sibling.getSortId();
+                    if (siblingOrder >= newOrder) {
+                        sibling.setSortId(siblingOrder + 1);
+                        sibling.setUpdatedTime(new Date());
+                        storeDictionaryMapper.updateById(sibling);
+                    }
+                } catch (NumberFormatException e) {
+                    log.warn("无法解析sortId: {}", sibling.getSortId(), e);
+                }
+            }
+        } catch (NumberFormatException e) {
+            log.error("移入节点排序调整失败,sortId格式错误: newSortId={}", newSortId, e);
+        } catch (Exception e) {
+            log.error("移入节点排序调整失败", e);
+        }
+    }
+
+    /**
+     * 批量导入筛选条件
+     *
+     * @param file Excel文件
+     * @return 导入结果
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public R<String> importFilterCondition(MultipartFile file)  {
+        log.info("PlatformBusinessSectionServiceImpl.importBusinessSection fileName={}",
+                file != null ? file.getOriginalFilename() : "null");
+        try {
+            if (file == null || file.isEmpty()) {
+                return R.fail("文件不能为空");
+            }
+
+            String fileName = file.getOriginalFilename();
+            if (fileName == null || (!fileName.endsWith(".xlsx") && !fileName.endsWith(".xls"))) {
+                return R.fail("文件格式不正确,请上传Excel文件");
+            }
+
+            List<String> errorMessages = new ArrayList<>();
+            int successCount = 0;
+            int totalCount = 0;
+
+            // 使用POI读取Excel
+            try (InputStream inputStream = file.getInputStream();
+                 Workbook workbook = new XSSFWorkbook(inputStream)) {
+                Sheet sheet = workbook.getSheetAt(0);
+
+                // 获取表头
+                Row headerRow = sheet.getRow(5);
+                if (headerRow == null) {
+                    return R.fail("Excel文件格式不正确,缺少表头");
+                }
+
+                // 构建字段映射(表头名称 -> 列索引)
+                Map<String, Integer> headerMap = new HashMap<>();
+                Field[] fields = DictionaryLibraryExcelVo.class.getDeclaredFields();
+                for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+                    Cell cell = headerRow.getCell(i);
+                    if (cell != null) {
+                        String headerName = getCellValueAsString(cell);
+                        if (StringUtils.isNotBlank(headerName)) {
+                            headerMap.put(headerName.trim(), i);
+                        }
+                    }
+                }
+
+                // 读取数据行
+                for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
+                    Row row = sheet.getRow(rowIndex);
+                    if (row == null) {
+                        continue;
+                    }
+
+                    // 检查是否为空行
+                    boolean isEmptyRow = true;
+                    for (int i = 0; i < row.getLastCellNum(); i++) {
+                        Cell cell = row.getCell(i);
+                        if (cell != null && cell.getCellType() != CellType.BLANK) {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                isEmptyRow = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (isEmptyRow) {
+                        continue;
+                    }
+
+                    totalCount++;
+                    DictionaryLibraryExcelVo excelVo = new DictionaryLibraryExcelVo();
+
+                    // 读取每个字段
+                    for (Field field : fields) {
+                        if (!field.isAnnotationPresent(ExcelHeader.class)) {
+                            continue;
+                        }
+                        ExcelHeader excelHeader = field.getAnnotation(ExcelHeader.class);
+                        String headerName = excelHeader.value();
+                        Integer colIndex = headerMap.get(headerName);
+                        if (colIndex == null) {
+                            continue;
+                        }
+
+                        Cell cell = row.getCell(colIndex);
+                        if (cell == null) {
+                            continue;
+                        }
+
+                        field.setAccessible(true);
+                        try {
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotBlank(cellValue)) {
+                                field.set(excelVo, cellValue.trim());
+                            }
+                        } catch (Exception e) {
+                            log.warn("读取字段{}失败:{}", headerName, e.getMessage());
+                        }
+                    }
+
+                    // 处理导入数据
+                    try {
+                        processImportData(excelVo, rowIndex + 1, errorMessages);
+                        successCount++;
+                    } catch (Exception e) {
+                        errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
+                        log.error("导入第{}行数据失败", rowIndex + 1, e);
+                    }
+                }
+            }
+
+            // 构建返回消息
+            StringBuilder message = new StringBuilder();
+            message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
+            if (!errorMessages.isEmpty()) {
+                message.append("\n失败详情:\n");
+                int maxErrors = Math.min(errorMessages.size(), 10); // 最多显示10条错误
+                for (int i = 0; i < maxErrors; i++) {
+                    message.append(errorMessages.get(i)).append("\n");
+                }
+                if (errorMessages.size() > 10) {
+                    message.append(String.format("...还有%d条错误未显示", errorMessages.size() - 10));
+                }
+            }
+
+            if (successCount == 0) {
+                return R.fail(message.toString());
+            } else if (totalCount - successCount > 0) {
+                return R.success(message.toString());
+            } else {
+                return R.success(message.toString());
+            }
+        } catch (Exception e) {
+            log.error("导入经营版块失败", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取单元格值(字符串格式)
+     *
+     * @param cell 单元格
+     * @return 字符串值
+     */
+    private String getCellValueAsString(Cell cell) {
+        if (cell == null) {
+            return null;
+        }
+
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    return cell.getDateCellValue().toString();
+                } else {
+                    // 处理数字,避免科学计数法
+                    double numericValue = cell.getNumericCellValue();
+                    if (numericValue == (long) numericValue) {
+                        return String.valueOf((long) numericValue);
+                    } else {
+                        return String.valueOf(numericValue);
+                    }
+                }
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                return cell.getCellFormula();
+            default:
+                return null;
+        }
+    }
+
+
+    /**
+     * 处理导入数据
+     *
+     * @param excelVo Excel数据对象
+     * @param rowIndex 行号
+     * @param errorMessages 错误消息列表
+     */
+    private void processImportData(DictionaryLibraryExcelVo excelVo, int rowIndex, List<String> errorMessages) {
+        // 校验必填字段
+        if (StringUtils.isBlank(excelVo.getFirstLevelName())) {
+            throw new IllegalArgumentException("一级分类名称不能为空");
+        }
+
+        String firstLevelName = excelVo.getFirstLevelName().trim();
+        String secondLevelName = StringUtils.isNotBlank(excelVo.getSecondLevelName())
+                ? excelVo.getSecondLevelName().trim() : null;
+        String thirdLevelName = StringUtils.isNotBlank(excelVo.getThirdLevelName())
+                ? excelVo.getThirdLevelName().trim() : null;
+        String fourLevelName = StringUtils.isNotBlank(excelVo.getFourLevelName())
+                ? excelVo.getFourLevelName().trim() : null;
+
+        // 确定要创建的级别
+        if (StringUtils.isNotBlank(fourLevelName)) {
+            // 创建四级分类
+            createOrUpdateLevel(firstLevelName, secondLevelName, thirdLevelName, fourLevelName, 4, excelVo.getHidden(), errorMessages, rowIndex);
+        } else if (StringUtils.isNotBlank(thirdLevelName)) {
+            // 创建三级分类
+            createOrUpdateLevel(firstLevelName, secondLevelName, thirdLevelName, null, 3, excelVo.getHidden(), errorMessages, rowIndex);
+        } else if (StringUtils.isNotBlank(secondLevelName)) {
+            // 创建二级分类
+            createOrUpdateLevel(firstLevelName, secondLevelName, null, null, 2, excelVo.getHidden(), errorMessages, rowIndex);
+        } else {
+            // 创建一级分类
+            createOrUpdateLevel(firstLevelName, null, null, null, 1, excelVo.getHidden(), errorMessages, rowIndex);
+        }
+    }
+
+
+    /**
+     * 下载筛选条件导入模板
+     *
+     * @param response HTTP响应
+     * @throws IOException IO异常
+     */
+    @Override
+    public void downloadTemplate(HttpServletResponse response) throws IOException {
+        log.info("FilterConditionServiceImpl.downloadTemplate");
+        
+        XSSFWorkbook workbook = new XSSFWorkbook();
+        try {
+            Sheet sheet = workbook.createSheet("筛选条件导入模板");
+
+            // 创建表头样式
+            Font headerFont = workbook.createFont();
+            headerFont.setBold(true);
+            headerFont.setFontHeightInPoints((short) 12);
+            CellStyle headerCellStyle = workbook.createCellStyle();
+            headerCellStyle.setFont(headerFont);
+            headerCellStyle.setAlignment(HorizontalAlignment.CENTER);
+            headerCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
+            headerCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
+            headerCellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
+            headerCellStyle.setBorderBottom(BorderStyle.THIN);
+            headerCellStyle.setBorderTop(BorderStyle.THIN);
+            headerCellStyle.setBorderLeft(BorderStyle.THIN);
+            headerCellStyle.setBorderRight(BorderStyle.THIN);
+
+            // 创建表头
+            Row headerRow = sheet.createRow(0);
+            String[] headers = {"一级分类名称", "二级分类名称", "三级分类名称", "四级分类名称", "状态(隐藏/显示)"};
+            for (int i = 0; i < headers.length; i++) {
+                Cell cell = headerRow.createCell(i);
+                cell.setCellValue(headers[i]);
+                cell.setCellStyle(headerCellStyle);
+            }
+
+            // 设置列宽
+            sheet.setColumnWidth(0, 25 * 256); // 一级分类名称
+            sheet.setColumnWidth(1, 25 * 256); // 二级分类名称
+            sheet.setColumnWidth(2, 25 * 256); // 三级分类名称
+            sheet.setColumnWidth(3, 25 * 256); // 四级分类名称
+            sheet.setColumnWidth(4, 15 * 256); // 状态(隐藏/显示)
+
+            // 设置响应头(必须在获取输出流之前设置)
+            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+            response.setCharacterEncoding("utf-8");
+            String fileName = URLEncoder.encode("筛选条件导入模板", "UTF-8").replaceAll("\\+", "%20");
+            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
+
+            // 输出到响应流
+            OutputStream outputStream = response.getOutputStream();
+            workbook.write(outputStream);
+            outputStream.flush();
+        } finally {
+            // 确保 workbook 被正确关闭
+            if (workbook != null) {
+                try {
+                    workbook.close();
+                } catch (IOException e) {
+                    log.error("关闭workbook失败", e);
+                }
+            }
+        }
+    }
+
+
+    /**
+     * 创建或更新分类级别
+     *
+     * @param firstLevelName 一级分类名称
+     * @param secondLevelName 二级分类名称
+     * @param thirdLevelName 三级分类名称
+     * @param fourLevelName 四级分类名称
+     * @param level 级别(1、2、3、4)
+     * @param hidden 状态
+     * @param errorMessages 错误消息列表
+     * @param rowIndex 行号
+     */
+    private void createOrUpdateLevel(String firstLevelName, String secondLevelName, String thirdLevelName,
+                                     String fourLevelName, int level, String hidden, List<String> errorMessages, int rowIndex) {
+        try {
+            // 查找或创建一级分类
+            StoreDictionary firstLevel = findOrCreateLevel(firstLevelName, null, 0, hidden);
+            if (firstLevel == null) {
+                throw new IllegalArgumentException("创建一级分类失败");
+            }
+            if (level >= 2 && StringUtils.isNotBlank(secondLevelName)) {
+                // 查找或创建二级分类
+                StoreDictionary secondLevel = findOrCreateLevel(secondLevelName, firstLevel.getId(), 1, hidden);
+                if (secondLevel == null) {
+                    throw new IllegalArgumentException("创建二级分类失败");
+                }
+
+                if (level >= 3 && StringUtils.isNotBlank(thirdLevelName)) {
+                    // 查找或创建三级分类
+                    StoreDictionary thirdLevel = findOrCreateLevel(thirdLevelName, secondLevel.getId(), 2, hidden);
+                    if (thirdLevel == null) {
+                        throw new IllegalArgumentException("创建三级分类失败");
+                    }
+
+                    if (level >= 4 && StringUtils.isNotBlank(fourLevelName)) {
+                        // 查找或创建四级分类
+                        StoreDictionary fourLevel = findOrCreateLevel(fourLevelName, thirdLevel.getId(), 3, hidden);
+                        if (fourLevel == null) {
+                            throw new IllegalArgumentException("创建四级分类失败");
+                        }
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new IllegalArgumentException(e.getMessage());
+        }
+    }
+
+    /**
+     * 查找或创建分类
+     *
+     * @param dictDetail 分类名称
+     * @param parentId 父节点ID
+     * @param expectedLevel 期望的级别(用于校验)
+     * @param hidden 状态
+     * @return 分类对象
+     */
+    private StoreDictionary findOrCreateLevel(String dictDetail, Integer parentId, int expectedLevel, String hidden) {
+        // 查找是否存在
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.in(StoreDictionary::getTypeName, "filter_condition","filter_condition_type","filter_condition_classify", "filter_condition_timeRange");
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+        queryWrapper.eq(StoreDictionary::getDictDetail, dictDetail);
+
+        if (parentId == null || parentId == 0) {
+            queryWrapper.and(wrapper -> wrapper.isNull(StoreDictionary::getParentId)
+                    .or().eq(StoreDictionary::getParentId, 0));
+        } else {
+            queryWrapper.eq(StoreDictionary::getParentId, parentId);
+        }
+
+        StoreDictionary existing = storeDictionaryMapper.selectOne(queryWrapper);
+
+        if (existing != null) {
+            return existing;
+        }
+
+        // 创建新分类
+        StoreDictionary newDict = new StoreDictionary();
+        newDict.setDictDetail(dictDetail);
+        // 一级为筛选条件,二级为筛选条件种类,三级为分类,四级为时间范围
+        if (expectedLevel == 0) {
+            newDict.setTypeDetail("筛选条件");
+            newDict.setTypeName("filter_condition");
+        } else if (expectedLevel == 1) {
+            newDict.setTypeDetail("筛选条件种类");
+            newDict.setTypeName("filter_condition_type");
+        } else if (expectedLevel == 2) {
+            newDict.setTypeDetail("分类");
+            newDict.setTypeName("filter_condition_classify");
+        } else if (expectedLevel == 3) {
+            newDict.setTypeDetail("时间范围");
+            newDict.setTypeName("filter_condition_timeRange");
+        }
+        newDict.setParentId(parentId == null ? null : parentId);
+        newDict.setHidden(StringUtils.isNotBlank(hidden) && hidden.equals("隐藏") ? 1 : 0);
+        newDict.setDeleteFlag(0);
+        newDict.setCreatedTime(new Date());
+
+        // 生成 dict_id
+        String maxDictId = getMaxDictIdByTypeName(newDict.getTypeName());
+        newDict.setDictId(String.valueOf(maxDictId == null ? 1 : Integer.parseInt(maxDictId) + 1));
+
+        // 生成 sort_id
+        Integer maxSortId = getMaxSortIdByParentId(newDict.getParentId());
+        newDict.setSortId(maxSortId == null ? 1 : maxSortId + 1);
+
+        boolean saved = this.save(newDict);
+        return saved ? newDict : null;
+    }
+
+    /**
+     * 根据 typeName 获取最大 dict_id
+     *
+     * @param typeName 类型名称
+     * @return 最大 dict_id,如果不存在则返回 null
+     */
+    private String getMaxDictIdByTypeName(String typeName) {
+        LambdaQueryWrapper<StoreDictionary> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreDictionary::getTypeName, typeName);
+        queryWrapper.eq(StoreDictionary::getDeleteFlag, 0);
+
+        queryWrapper.orderByDesc(StoreDictionary::getDictId);
+        queryWrapper.last("LIMIT 1");
+
+        StoreDictionary maxDict = storeDictionaryMapper.selectOne(queryWrapper);
+        return maxDict != null ? maxDict.getDictId() : null;
+    }
+}
+

BIN
alien-store/src/main/resources/templates/筛选条件导入模版.xlsx