qrs 2 тижнів тому
батько
коміт
632fc10568

+ 46 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/StoreMenuImportVo.java

@@ -0,0 +1,46 @@
+package shop.alien.entity.store.vo;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+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.ExcelImage;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 二期-门店菜单
+ *
+ * @author ssk
+ * @since 2024-12-05
+ */
+@Data
+@JsonInclude
+@ApiModel(value = "StoreMenuImportVo对象")
+public class StoreMenuImportVo {
+
+    @ApiModelProperty(value = "菜品名称")
+    private String dishName;
+
+    @ApiModelProperty(value = "价格")
+    private BigDecimal dishPrice;
+
+    @ApiModelProperty(value = "成本价")
+    private BigDecimal costPrice;
+
+    @ApiModelProperty(value = "单位")
+    private String dishesUnit;
+
+    @ApiModelProperty(value = "图片")
+    @ExcelImage
+    private String img;
+
+    @ApiModelProperty(value = "描述")
+    private String description;
+
+    @ApiModelProperty(value = "是否推荐")
+    private Integer dishType;
+}

+ 72 - 13
alien-store-platform/src/main/java/shop/alien/storeplatform/controller/StoreMenuPlatformController.java

@@ -1,25 +1,22 @@
 package shop.alien.storeplatform.controller;
 
-import io.swagger.annotations.Api;
-import io.swagger.annotations.ApiImplicitParam;
-import io.swagger.annotations.ApiImplicitParams;
-import io.swagger.annotations.ApiOperation;
-import io.swagger.annotations.ApiOperationSupport;
-import io.swagger.annotations.ApiSort;
+import io.swagger.annotations.*;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-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.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.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.StoreMenu;
 import shop.alien.entity.store.vo.StoreMenuVo;
 import shop.alien.storeplatform.service.StoreMenuPlatformService;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
 import java.util.List;
 
 /**
@@ -125,6 +122,68 @@ public class StoreMenuPlatformController {
         log.info("StoreMenuController.getMenuLikeStatus?userId={}&menuId={}", userId, menuId);
         return R.data(storeMenuService.getMenuLikeStatus(userId, menuId));
     }
+
+    @ApiOperation("Excel导入门店菜单")
+    @ApiOperationSupport(order = 9)
+    @ApiImplicitParams({
+            @ApiImplicitParam(name = "file", value = "Excel文件", dataType = "file", paramType = "form", required = true),
+            @ApiImplicitParam(name = "storeId", value = "门店id", dataType = "Integer", paramType = "query", required = true)
+    })
+    @GetMapping("/importMenu")
+    public R<String> importMenu(@RequestParam("file") MultipartFile file,
+                                @RequestParam("storeId") Integer storeId) {
+        log.info("StoreMenuPlatformController.importMenu?storeId={}", storeId);
+        return storeMenuService.importMenuFromExcel(file, storeId);
+    }
+
+    @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);
+            }
+        }
+    }
 }
 
 

+ 86 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/entity/BathFacilityService.java

@@ -0,0 +1,86 @@
+package shop.alien.storeplatform.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 洗浴设施及服务表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("bath_facility_service")
+@ApiModel(value = "BathFacilityService对象", description = "洗浴设施及服务表")
+public class BathFacilityService implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "设施分类(1:洗浴区, 2:汗蒸区, 3:休闲区, 4:餐饮区)")
+    @TableField("facility_category")
+    private Integer facilityCategory;
+
+    @ApiModelProperty(value = "名称")
+    @TableField("facility_name")
+    private String facilityName;
+
+    @ApiModelProperty(value = "收费标准")
+    @TableField("charging_standard")
+    private String chargingStandard;
+
+    @ApiModelProperty(value = "使用时间类型(0:全天, 1:选择时间)")
+    @TableField("usage_time_type")
+    private Integer usageTimeType;
+
+    @ApiModelProperty(value = "使用开始时间(HH:mm格式)")
+    @TableField("usage_start_time")
+    private String usageStartTime;
+
+    @ApiModelProperty(value = "使用结束时间(HH:mm格式)")
+    @TableField("usage_end_time")
+    private String usageEndTime;
+
+    @ApiModelProperty(value = "是否显示在店铺详情(0:隐藏, 1:显示)")
+    @TableField("display_in_store_detail")
+    private Integer displayInStoreDetail;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField(value = "created_user_id", fill = FieldFill.INSERT)
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField(value = "updated_user_id", fill = FieldFill.INSERT_UPDATE)
+    private Integer updatedUserId;
+}
+

+ 82 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/entity/SportsEquipmentFacility.java

@@ -0,0 +1,82 @@
+package shop.alien.storeplatform.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 运动器材设施表
+ *
+ * @author system
+ * @since 2025-01-XX
+ */
+@Data
+@JsonInclude
+@TableName("sports_equipment_facility")
+@ApiModel(value = "SportsEquipmentFacility对象", description = "运动器材设施表")
+public class SportsEquipmentFacility implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "门店ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "设施分类(1:有氧区, 2:力量区, 3:单功能机械区)")
+    @TableField("facility_category")
+    private Integer facilityCategory;
+
+    @ApiModelProperty(value = "设施名称")
+    @TableField("facility_name")
+    private String facilityName;
+
+    @ApiModelProperty(value = "数量")
+    @TableField("quantity")
+    private Integer quantity;
+
+    @ApiModelProperty(value = "品牌")
+    @TableField("brand")
+    private String brand;
+
+    @ApiModelProperty(value = "描述")
+    @TableField("description")
+    private String description;
+
+    @ApiModelProperty(value = "是否显示在店铺详情(0:隐藏, 1:显示)")
+    @TableField("display_in_store_detail")
+    private Integer displayInStoreDetail;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField(value = "created_user_id", fill = FieldFill.INSERT)
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField(value = "updated_user_id", fill = FieldFill.INSERT_UPDATE)
+    private Integer updatedUserId;
+}
+

+ 108 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/entity/StoreMenu.java

@@ -0,0 +1,108 @@
+package shop.alien.storeplatform.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 二期-门店菜单
+ *
+ * @author ssk
+ * @since 2024-12-05
+ */
+@Data
+@JsonInclude
+@TableName("store_menu")
+@ApiModel(value = "StoreMenu对象", description = "门店菜单")
+public class StoreMenu {
+
+    @ApiModelProperty(value = "主键")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "门店id")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "图片id")
+    @TableField("img_id")
+    private Integer imgId;
+
+    @ApiModelProperty(value = "菜单类型:1-菜单,2-酒水")
+    @TableField("dish_menu_type")
+    private String dishMenuType;
+
+    @ApiModelProperty(value = "名称")
+    @TableField("dish_name")
+    private String dishName;
+
+    @ApiModelProperty(value = "价格")
+    @TableField("dish_price")
+    private BigDecimal dishPrice;
+
+    @ApiModelProperty(value = "是否推荐, 0:非推荐, 1:推荐")
+    @TableField("dish_type")
+    private Integer dishType;
+
+    @ApiModelProperty(value = "点赞数")
+    @TableField("like_count")
+    private Integer likeCount;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+
+    @ApiModelProperty(value = "单位")
+    @TableField("dishes_unit")
+    private String dishesUnit;
+
+    @ApiModelProperty(value = "成本价")
+    @TableField("cost_price")
+    private BigDecimal costPrice;
+
+    @ApiModelProperty(value = "排序")
+    @TableField("sort")
+    private int sort;
+
+    @ApiModelProperty(value = "描述")
+    @TableField("description")
+    private String description;
+
+    @ApiModelProperty(value = "酒精度")
+    @TableField("alcoholVolume")
+    private String alcoholVolume;
+
+    @ApiModelProperty(value = "分类")
+    @TableField("category")
+    private String category;
+
+    @ApiModelProperty(value = "品味")
+    @TableField("flavor")
+    private String flavor;
+
+}

+ 103 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/entity/StoreStaffConfig.java

@@ -0,0 +1,103 @@
+package shop.alien.storeplatform.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.util.Date;
+
+/**
+ * @Author: fcw
+ * @CreateTime: 2025-04-11
+ * @Description: 员工管理
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@JsonInclude
+@TableName("store_staff_config")
+@ApiModel(value = "StoreStaffConfig对象", description = "员工管理")
+public class StoreStaffConfig extends Model<StoreStaffConfig> {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "主键id")
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    @ApiModelProperty(value = "员工职位")
+    @TableField("staff_position")
+    private String staffPosition;
+
+    @ApiModelProperty(value = "员工名称/昵称")
+    @TableField("name")
+    private String name;
+
+    @ApiModelProperty(value = "头像")
+    @TableField("staff_image")
+    private String staffImage;
+
+    @ApiModelProperty(value = "擅长项目")
+    @TableField("proficient_projects")
+    private String proficientProjects;
+
+    @ApiModelProperty(value = "标签")
+    @TableField("tag")
+    private String tag;
+
+    @ApiModelProperty(value = "个人简介")
+    @TableField("personal_introduction")
+    private String personalIntroduction;
+
+    @ApiModelProperty(value = "人员状态0-待审核 1-审核通过 2-审核拒绝")
+    @TableField("status")
+    private String status;
+
+    @ApiModelProperty(value = "店铺ID")
+    @TableField("store_id")
+    private Integer storeId;
+
+    @ApiModelProperty(value = "店铺名称")
+    @TableField("store_name")
+    private String storeName;
+
+    @ApiModelProperty(value = "拒绝原因")
+    @TableField("rejection_reason")
+    private String rejectionReason;
+
+    @ApiModelProperty(value = "删除标记, 0:未删除, 1:已删除")
+    @TableField("delete_flag")
+    @TableLogic
+    private Integer deleteFlag;
+
+    @ApiModelProperty(value = "创建时间")
+    @TableField(value = "created_time", fill = FieldFill.INSERT)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date createdTime;
+
+    @ApiModelProperty(value = "创建人ID")
+    @TableField("created_user_id")
+    private Integer createdUserId;
+
+    @ApiModelProperty(value = "修改时间")
+    @TableField(value = "updated_time", fill = FieldFill.UPDATE)
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date updatedTime;
+
+    @ApiModelProperty(value = "修改人ID")
+    @TableField("updated_user_id")
+    private Integer updatedUserId;
+
+    @ApiModelProperty(value = "置顶状态, 0:未置顶, 1:已置顶")
+    @TableField("top_status")
+    private Integer topStatus;
+
+    @ApiModelProperty(value = "置顶时间")
+    @TableField("top_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date topTime;
+}

+ 7 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/feign/AlienStoreFeign.java

@@ -2,8 +2,12 @@ package shop.alien.storeplatform.feign;
 
 import com.alibaba.fastjson.JSONObject;
 import org.springframework.cloud.openfeign.FeignClient;
+import org.springframework.http.MediaType;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RequestPart;
+import org.springframework.web.multipart.MultipartFile;
 
 @FeignClient(url = "${feign.alienStore.url}", name = "alien-store")
 public interface AlienStoreFeign {
@@ -13,4 +17,7 @@ public interface AlienStoreFeign {
             @RequestParam(value = "messageReceiverId") String messageReceiverId,
             @RequestParam(value = "webSocketVoStr") String webSocketVoStr);
 
+    @PostMapping(value = "/file/uploadMore", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    JSONObject uploadFile(@RequestPart("multipartRequest") MultipartFile multipartRequest);
+
 }

+ 10 - 0
alien-store-platform/src/main/java/shop/alien/storeplatform/service/StoreMenuPlatformService.java

@@ -1,6 +1,7 @@
 package shop.alien.storeplatform.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
+import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.StoreMenu;
 import shop.alien.entity.store.vo.StoreMenuVo;
@@ -90,6 +91,15 @@ public interface StoreMenuPlatformService extends IService<StoreMenu> {
      * @return boolean
      */
     boolean getMenuLikeStatus(String userId, Integer menuId);
+
+    /**
+     * Excel导入门店菜单
+     *
+     * @param file     Excel文件
+     * @param storeId  门店id
+     * @return 导入结果
+     */
+    R<String> importMenuFromExcel(MultipartFile file, Integer storeId);
 }
 
 

+ 416 - 3
alien-store-platform/src/main/java/shop/alien/storeplatform/service/impl/StoreMenuPlatformServiceImpl.java

@@ -1,6 +1,7 @@
 package shop.alien.storeplatform.service.impl;
 
 import cn.hutool.core.collection.CollectionUtil;
+import com.alibaba.fastjson.JSONObject;
 import com.alibaba.nacos.common.utils.CollectionUtils;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -8,23 +9,34 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.xssf.usermodel.*;
 import org.springframework.beans.BeanUtils;
 import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
 import shop.alien.entity.result.R;
 import shop.alien.entity.store.LifeGroupBuyThali;
 import shop.alien.entity.store.LifeLikeRecord;
 import shop.alien.entity.store.StoreImg;
 import shop.alien.entity.store.StoreMenu;
+import shop.alien.entity.store.excelVo.util.ExcelImage;
+import shop.alien.entity.store.vo.StoreMenuImportVo;
 import shop.alien.entity.store.vo.StoreMenuVo;
 import shop.alien.mapper.LifeGroupBuyThaliMapper;
 import shop.alien.mapper.LifeLikeRecordMapper;
 import shop.alien.mapper.StoreImgMapper;
 import shop.alien.mapper.StoreMenuMapper;
+import shop.alien.storeplatform.feign.AlienStoreFeign;
 import shop.alien.storeplatform.service.StoreMenuPlatformService;
+import shop.alien.util.ali.AliOSSUtil;
 
-import java.util.Comparator;
-import java.util.List;
-import java.util.Map;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -33,6 +45,7 @@ import java.util.stream.Collectors;
  * @author ssk
  * @since 2024-12-05
  */
+@Slf4j
 @Service
 @RequiredArgsConstructor
 public class StoreMenuPlatformServiceImpl extends ServiceImpl<StoreMenuMapper, StoreMenu> implements StoreMenuPlatformService {
@@ -45,6 +58,10 @@ public class StoreMenuPlatformServiceImpl extends ServiceImpl<StoreMenuMapper, S
 
     private final LifeGroupBuyThaliMapper lifeGroupBuyThaliMapper;
 
+    private final AliOSSUtil aliOSSUtil;
+
+    private final AlienStoreFeign alienStoreFeign;
+
     /**
      * 获取门店菜单
      *
@@ -280,6 +297,402 @@ public class StoreMenuPlatformServiceImpl extends ServiceImpl<StoreMenuMapper, S
     public boolean getMenuLikeStatus(String userId, Integer menuId) {
         return lifeLikeRecordMapper.selectCount(new QueryWrapper<LifeLikeRecord>().eq("dianzan_id", userId).eq("huifu_id", menuId).eq("delete_flag", 0)) > 0;
     }
+    /**
+     * Excel导入门店菜单
+     *
+     * @param file     Excel文件
+     * @param storeId  门店id
+     * @return 导入结果
+     */
+    @Override
+    public R<String> importMenuFromExcel(MultipartFile file, Integer storeId) {
+        log.info("StoreMenuPlatformServiceImpl.importMenuFromExcel storeId={}", storeId);
+
+        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文件");
+        }
+
+        if (storeId == null) {
+            return R.fail("门店ID不能为空");
+        }
+
+        List<String> errorMessages = new ArrayList<>();
+        int successCount = 0;
+        int totalCount = 0;
+
+        try (InputStream inputStream = file.getInputStream();
+             XSSFWorkbook workbook = new XSSFWorkbook(inputStream)) {
+            Sheet sheet = workbook.getSheetAt(0);
+
+            // 获取表头
+            Row headerRow = sheet.getRow(0);
+            if (headerRow == null) {
+                return R.fail("Excel文件格式不正确,缺少表头");
+            }
+
+            // 构建字段映射(表头名称 -> 列索引)
+            Map<String, Integer> headerMap = new HashMap<>();
+            Field[] fields = StoreMenuImportVo.class.getDeclaredFields();
+            for (int i = 0; i < headerRow.getLastCellNum(); i++) {
+                Cell cell = headerRow.getCell(i);
+                if (cell != null) {
+                    String headerName = getCellValueAsString(cell);
+                    if (StringUtils.isNotEmpty(headerName)) {
+                        headerMap.put(headerName.trim(), i);
+                    }
+                }
+            }
+
+            // 获取图片映射(行索引 -> 图片字节数组)
+            Map<Integer, byte[]> imageMap = extractImagesFromSheet(sheet);
+
+            // 读取数据行
+            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.isNotEmpty(cellValue)) {
+                            isEmptyRow = false;
+                            break;
+                        }
+                    }
+                }
+                if (isEmptyRow) {
+                    continue;
+                }
+
+                totalCount++;
+                StoreMenuImportVo excelVo = new StoreMenuImportVo();
+
+                // 读取每个字段
+                for (Field field : fields) {
+                    field.setAccessible(true);
+                    String fieldName = field.getName();
+                    Integer colIndex = null;
+
+                    // 根据字段名查找对应的表头
+                    for (Map.Entry<String, Integer> entry : headerMap.entrySet()) {
+                        String headerName = entry.getKey();
+                        if (isFieldMatch(fieldName, headerName)) {
+                            colIndex = entry.getValue();
+                            break;
+                        }
+                    }
+
+                    if (colIndex == null) {
+                        continue;
+                    }
+
+                    Cell cell = row.getCell(colIndex);
+                    if (cell == null && !field.isAnnotationPresent(ExcelImage.class)) {
+                        continue;
+                    }
+
+                    try {
+                        if (field.isAnnotationPresent(ExcelImage.class)) {
+                            // 处理图片字段
+                            byte[] imageBytes = imageMap.get(rowIndex);
+                            if (imageBytes != null && imageBytes.length > 0) {
+                                String imageName = "menu_" + storeId + "_" + System.currentTimeMillis() + "_" + rowIndex + ".jpg";
+                                MultipartFile multipartFile = new ByteArrayMultipartFile(imageBytes, imageName);
+                                JSONObject jsonObject = alienStoreFeign.uploadFile(multipartFile);
+                                if (200 == jsonObject.getIntValue("code")) {
+                                    field.set(excelVo, jsonObject.getJSONArray("data").get(0));
+                                } else {
+                                    field.set(excelVo, "");
+                                }
+//                                String imageUrl = uploadImageToOSS(imageBytes, storeId, rowIndex);
+                            }
+                        } else {
+                            // 处理普通字段
+                            String cellValue = getCellValueAsString(cell);
+                            if (StringUtils.isNotEmpty(cellValue)) {
+                                setFieldValue(excelVo, field, cellValue.trim());
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.warn("读取字段{}失败:{}", fieldName, e.getMessage());
+                    }
+                }
+
+                // 处理导入数据
+                try {
+                    validateAndSaveMenu(excelVo, storeId, rowIndex + 1);
+                    successCount++;
+                } catch (Exception e) {
+                    errorMessages.add(String.format("第%d行:%s", rowIndex + 1, e.getMessage()));
+                    log.error("导入第{}行数据失败", rowIndex + 1, e);
+                }
+            }
+        } catch (Exception e) {
+            log.error("导入Excel失败", e);
+            return R.fail("导入失败:" + e.getMessage());
+        }
+
+        // 构建返回消息
+        StringBuilder message = new StringBuilder();
+        message.append(String.format("导入完成:成功%d条,失败%d条", successCount, totalCount - successCount));
+        if (!errorMessages.isEmpty()) {
+            message.append("\n失败详情:\n");
+            for (int i = 0; i < Math.min(errorMessages.size(), 10); i++) {
+                message.append(errorMessages.get(i)).append("\n");
+            }
+            if (errorMessages.size() > 10) {
+                message.append("...还有").append(errorMessages.size() - 10).append("条错误信息");
+            }
+        }
+
+        return R.success(message.toString());
+    }
+
+    /**
+     * 从Sheet中提取图片
+     */
+    private Map<Integer, byte[]> extractImagesFromSheet(Sheet sheet) {
+        Map<Integer, byte[]> imageMap = new HashMap<>();
+        if (sheet instanceof XSSFSheet) {
+            XSSFSheet xssfSheet = (XSSFSheet) sheet;
+            XSSFDrawing drawing = xssfSheet.getDrawingPatriarch();
+            if (drawing != null) {
+                List<XSSFShape> shapes = drawing.getShapes();
+                for (XSSFShape shape : shapes) {
+                    if (shape instanceof XSSFPicture) {
+                        XSSFPicture picture = (XSSFPicture) shape;
+                        XSSFClientAnchor anchor = (XSSFClientAnchor) picture.getAnchor();
+                        int rowIndex = anchor.getRow1();
+                        try {
+                            // 直接使用 getData() 方法获取图片字节数组
+                            byte[] imageBytes = picture.getPictureData().getData();
+                            imageMap.put(rowIndex, imageBytes);
+                        } catch (Exception e) {
+                            log.warn("提取第{}行图片失败:{}", rowIndex, e.getMessage());
+                        }
+                    }
+                }
+            }
+        }
+        return imageMap;
+    }
+
+    /**
+     * 上传图片到OSS
+     */
+    private String uploadImageToOSS(byte[] imageBytes, Integer storeId, int rowIndex) {
+        try {
+            // 生成文件名
+            String fileName = "menu_" + storeId + "_" + System.currentTimeMillis() + "_" + rowIndex + ".jpg";
+            String prefix = "image/";
+
+            // 创建临时MultipartFile
+            MultipartFile multipartFile = new ByteArrayMultipartFile(imageBytes, fileName);
+
+            // 上传到OSS
+            return aliOSSUtil.uploadFile(multipartFile, prefix + fileName);
+        } catch (Exception e) {
+            log.error("上传图片失败", e);
+            throw new RuntimeException("上传图片失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 校验并保存菜单
+     */
+    private void validateAndSaveMenu(StoreMenuImportVo excelVo, Integer storeId, int rowNum) {
+        // 校验必填字段
+        if (StringUtils.isEmpty(excelVo.getDishName())) {
+            throw new RuntimeException("菜品名称不能为空");
+        }
+
+        if (excelVo.getDishPrice() == null || excelVo.getDishPrice().compareTo(BigDecimal.ZERO) <= 0) {
+            throw new RuntimeException("菜品价格必须大于0");
+        }
+
+        // 创建StoreMenu对象
+        StoreMenu storeMenu = new StoreMenu();
+        storeMenu.setStoreId(storeId);
+        storeMenu.setDishName(excelVo.getDishName());
+        storeMenu.setDishPrice(excelVo.getDishPrice());
+        storeMenu.setCostPrice(excelVo.getCostPrice());
+        storeMenu.setDishesUnit(excelVo.getDishesUnit());
+        storeMenu.setDescription(excelVo.getDescription());
+        storeMenu.setDishType(excelVo.getDishType());
+
+        // 处理图片
+        if (StringUtils.isNotEmpty(excelVo.getImg())) {
+            StoreImg storeImg = new StoreImg();
+            storeImg.setStoreId(storeId);
+            storeImg.setImgType(7);
+            storeImg.setImgUrl(excelVo.getImg());
+            storeImg.setImgDescription(excelVo.getDishName());
+            storeImgMapper.insert(storeImg);
+            storeMenu.setImgId(storeImg.getId());
+        }
+
+        // 设置排序
+        LambdaQueryWrapper<StoreMenu> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(StoreMenu::getStoreId, storeId);
+        List<StoreMenu> menuList = this.list(queryWrapper);
+        if (CollectionUtil.isNotEmpty(menuList)) {
+            int maxSort = menuList.stream().map(StoreMenu::getSort).max(Integer::compareTo).orElse(0);
+            storeMenu.setSort(maxSort + 1);
+        } else {
+            storeMenu.setSort(1);
+        }
+
+        // 保存菜单
+        boolean flag = this.save(storeMenu);
+        if (!flag) {
+            throw new RuntimeException("保存菜单失败");
+        }
+    }
+
+    /**
+     * 判断字段名是否匹配表头
+     */
+    private boolean isFieldMatch(String fieldName, String headerName) {
+        // 简单的匹配逻辑,可以根据实际需求调整
+        Map<String, String> fieldMapping = new HashMap<>();
+        fieldMapping.put("dishName", "菜品名称");
+        fieldMapping.put("dishPrice", "价格");
+        fieldMapping.put("costPrice", "成本价");
+        fieldMapping.put("dishesUnit", "单位");
+        fieldMapping.put("img", "图片");
+        fieldMapping.put("description", "描述");
+        fieldMapping.put("dishType", "是否推荐");
+
+        String expectedHeader = fieldMapping.get(fieldName);
+        return expectedHeader != null && expectedHeader.equals(headerName);
+    }
+
+    /**
+     * 设置字段值
+     */
+    private void setFieldValue(StoreMenuImportVo excelVo, Field field, String cellValue) throws Exception {
+        Class<?> fieldType = field.getType();
+        String fieldName = field.getName();
+        if (fieldType == BigDecimal.class) {
+            try {
+                if (new BigDecimal(cellValue).scale() > 2) {
+                    throw new RuntimeException("价格或者成本价格式错误, 请输入数字, 小数点保留两位以内");
+                } else {
+                    field.set(excelVo, new BigDecimal(cellValue));
+                }
+            } catch (NumberFormatException e) {
+                throw new RuntimeException("价格或者成本价格式错误, 请输入数字, 小数点保留两位以内");
+            }
+        } else {
+            if ("dishType".equals(fieldName)) {
+                String trimmedValue = cellValue.trim();
+                if ("是".equals(trimmedValue)) {
+                    field.set(excelVo, 1);
+                } else if ("否".equals(trimmedValue)) {
+                    field.set(excelVo, 0);
+                } else {
+                    throw new RuntimeException("是否推荐字段格式错误,请输入'是'或'否'");
+                }
+            } else {
+                field.set(excelVo, cellValue);
+            }
+        }
+    }
+
+    /**
+     * 获取单元格值(字符串格式)
+     */
+    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;
+        }
+    }
+
+    /**
+     * 临时MultipartFile实现类,用于上传字节数组
+     */
+    private static class ByteArrayMultipartFile implements MultipartFile {
+        private final byte[] content;
+        private final String fileName;
+
+        public ByteArrayMultipartFile(byte[] content, String fileName) {
+            this.content = content;
+            this.fileName = fileName;
+        }
+
+        @Override
+        public String getName() {
+            return "file";
+        }
+
+        @Override
+        public String getOriginalFilename() {
+            return fileName;
+        }
+
+        @Override
+        public String getContentType() {
+            return "image/jpeg";
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return content == null || content.length == 0;
+        }
+
+        @Override
+        public long getSize() {
+            return content.length;
+        }
+
+        @Override
+        public byte[] getBytes() throws IOException {
+            return content;
+        }
+
+        @Override
+        public InputStream getInputStream() throws IOException {
+            return new ByteArrayInputStream(content);
+        }
+
+        @Override
+        public void transferTo(java.io.File dest) throws IOException, IllegalStateException {
+            java.nio.file.Files.write(dest.toPath(), content);
+        }
+    }
 }
 
 

BIN
alien-store-platform/src/main/resources/templates/门店菜单导入模板.xlsx