فهرست منبع

feat(product): 新增商品导入导出功能和ERP数据拉取接口

- 实现商品基础信息、完整信息、图片详情的导入导出功能
- 添加商品导入模板下载和数据验证机制
- 集成EasyExcel实现批量数据处理
- 新增ERP系统多种数据类型的拉取同步接口
- 完善商品分类、品牌、单位等基础数据关联逻辑
- 实现税收编码远程查询和映射功能
- 添加操作日志记录和错误处理机制
肖路 7 ساعت پیش
والد
کامیت
2d73ab4b79
16فایلهای تغییر یافته به همراه2099 افزوده شده و 109 حذف شده
  1. 27 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteTaxCodeService.java
  2. 85 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteTaxCodeVo.java
  3. 119 0
      ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SerialNumberStrategy.java
  4. 230 0
      ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/erp/ErpPullController.java
  5. 220 11
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductBaseController.java
  6. 1 1
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductBaseBo.java
  7. 239 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductBaseImportVo.java
  8. 211 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductFullImportVo.java
  9. 46 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductImageExportVo.java
  10. 54 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductImageImportVo.java
  11. 288 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/listener/ProductBaseImportListener.java
  12. 269 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/listener/ProductFullImportListener.java
  13. 137 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/listener/ProductImageImportListener.java
  14. 89 76
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBaseServiceImpl.java
  15. 20 21
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBrandServiceImpl.java
  16. 64 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteTaxCodeServiceImpl.java

+ 27 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteTaxCodeService.java

@@ -0,0 +1,27 @@
+package org.dromara.system.api;
+
+import org.dromara.system.api.domain.vo.RemoteTaxCodeVo;
+
+import java.util.List;
+
+/**
+ * 税率编码
+ * @author
+ * @date 2026/6/5 下午3:03
+ */
+public interface RemoteTaxCodeService {
+    /**
+    * 通过税率编码编号获取税率编码对象
+    * */
+    RemoteTaxCodeVo selectByTaxCodeNo(String taxCodeNo);
+
+    /**
+    * 通过税率Id获取税率编码对象
+    * */
+    RemoteTaxCodeVo selectByTaxCodeId(Long taxCodeId);
+
+    /**
+    * 获取所有的税率编码对象
+    * */
+    List<RemoteTaxCodeVo> selectAll(RemoteTaxCodeVo remoteTaxCodeVo);
+}

+ 85 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteTaxCodeVo.java

@@ -0,0 +1,85 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * @author
+ * @date 2026/6/5 下午3:04
+ */
+@Data
+@NoArgsConstructor
+public class RemoteTaxCodeVo  implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     *
+     */
+    private Long id;
+
+    /**
+     * 父级id
+     */
+    private Long parentId;
+
+    /**
+     * 货物和劳务名称
+     */
+    private String name;
+
+    /**
+     * 商品和服务分类简称
+     */
+    private String abbreviation;
+
+    /**
+     * 税收编码
+     */
+    private String taxationNo;
+
+    /**
+     * 合并编码
+     */
+    private String mergeNo;
+
+    /**
+     * 上级编码
+     */
+    private String parentNo;
+
+    /**
+     * 祖级列表
+     */
+    private String ancestors;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 是否有下级 0不存在 1存在
+     * */
+    private String isBottom;
+
+    /**
+     * 税率
+     * */
+    private BigDecimal taxrate;
+    /**
+     * 说明
+     * */
+    private String explain;
+
+}

+ 119 - 0
ruoyi-auth/src/main/java/org/dromara/auth/service/impl/SerialNumberStrategy.java

@@ -0,0 +1,119 @@
+package org.dromara.auth.service.impl;
+
+import cn.dev33.satoken.stp.StpUtil;
+import cn.dev33.satoken.stp.parameter.SaLoginParameter;
+import cn.hutool.core.collection.CollUtil;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.zhyd.oauth.model.AuthResponse;
+import me.zhyd.oauth.model.AuthUser;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.auth.domain.vo.LoginVo;
+import org.dromara.auth.form.SocialLoginBody;
+import org.dromara.auth.service.IAuthStrategy;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.json.utils.JsonUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.social.config.properties.SocialProperties;
+import org.dromara.common.social.utils.SocialUtils;
+import org.dromara.common.tenant.helper.TenantHelper;
+import org.dromara.system.api.RemoteSocialService;
+import org.dromara.system.api.RemoteUserService;
+import org.dromara.system.api.domain.vo.RemoteClientVo;
+import org.dromara.system.api.domain.vo.RemoteSocialVo;
+import org.dromara.system.api.model.LoginUser;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * 第三方授权策略
+ *
+ * @author thiszhc is 三三
+ */
+@Slf4j
+@Service("serialNumber" + IAuthStrategy.BASE_NAME)
+@RequiredArgsConstructor
+public class SerialNumberStrategy implements IAuthStrategy {
+
+    private final SocialProperties socialProperties;
+
+    @DubboReference
+    private RemoteSocialService remoteSocialService;
+    @DubboReference
+    private RemoteUserService remoteUserService;
+
+    /**
+     * 登录-第三方授权登录
+     *
+     * @param body     登录信息
+     * @param client   客户端信息
+     */
+    @Override
+    public LoginVo login(String body, RemoteClientVo client) {
+        SocialLoginBody loginBody = JsonUtils.parseObject(body, SocialLoginBody.class);
+        ValidatorUtils.validate(loginBody);
+        AuthResponse<AuthUser> response = SocialUtils.loginAuth(
+            loginBody.getSource(), loginBody.getSocialCode(),
+            loginBody.getSocialState(), socialProperties);
+        if (!response.ok()) {
+            throw new ServiceException(response.getMsg());
+        }
+        AuthUser authUserData = response.getData();
+
+        List<RemoteSocialVo> list = remoteSocialService.selectByAuthId(authUserData.getSource() + authUserData.getUuid());
+        if (CollUtil.isEmpty(list)) {
+            throw new ServiceException("你还没有绑定第三方账号,绑定后才可以登录!");
+        }
+        RemoteSocialVo socialVo;
+        if (TenantHelper.isEnable()) {
+            Optional<RemoteSocialVo> opt = StreamUtils.findAny(list, x -> x.getTenantId().equals(loginBody.getTenantId()));
+            if (opt.isEmpty()) {
+                throw new ServiceException("对不起,你没有权限登录当前租户!");
+            }
+            socialVo = opt.get();
+        } else {
+            socialVo = list.get(0);
+        }
+
+        LoginUser loginUser = remoteUserService.getUserInfo(socialVo.getUserId(), socialVo.getTenantId());
+        loginUser.setClientKey(client.getClientKey());
+        loginUser.setDeviceType(client.getDeviceType());
+        SaLoginParameter model = new SaLoginParameter();
+        model.setDeviceType(client.getDeviceType());
+        // 自定义分配 不同用户体系 不同 token 授权时间 不设置默认走全局 yml 配置
+        // 例如: 后台用户30分钟过期 app用户1天过期
+        model.setTimeout(client.getTimeout());
+        model.setActiveTimeout(client.getActiveTimeout());
+        model.setExtra(LoginHelper.CLIENT_KEY, client.getClientId());
+        // 生成token
+        LoginHelper.login(loginUser, model);
+
+        LoginVo loginVo = new LoginVo();
+        loginVo.setAccessToken(StpUtil.getTokenValue());
+        loginVo.setExpireIn(StpUtil.getTokenTimeout());
+        loginVo.setClientId(client.getClientId());
+        return loginVo;
+    }
+
+    /**
+     * 客户登录
+     *
+     * @param body   登录对象
+     * @param client 授权管理视图对象
+     * @return 登录验证信息
+     */
+    @Override
+    public LoginVo clientLogin(String body, RemoteClientVo client) {
+        return null;
+    }
+
+    @Override
+    public LoginVo getToken(String username, String password) {
+        return null;
+    }
+
+}

+ 230 - 0
ruoyi-modules/ruoyi-external/src/main/java/org/dromara/external/controller/erp/ErpPullController.java

@@ -7,6 +7,8 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.io.FileUtils;
 import org.apache.dubbo.config.annotation.DubboReference;
 import org.dromara.common.core.domain.R;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
 import org.dromara.customer.api.RemoteErpCustomerService;
 import org.dromara.external.api.erp.domain.*;
 import org.dromara.product.api.RemoteErpProductService;
@@ -43,7 +45,11 @@ public class ErpPullController {
     private RemoteErpSystemService remoteErpSystemService;
 
     @PostMapping()
+    @Log(title = "ero数据拉取", businessType = BusinessType.OTHER)
     public R<Void> erp(@RequestParam String type, @RequestBody Map<String,Object> map){
+        log.info("ERP数据拉取: " + type);
+        log.info("ERP数据拉取参数: " + map);
+        //公司
         if("syncCompany".equals(type)){
             ErpCompany bean = BeanUtil.toBean(map, ErpCompany.class);
             this.syncCompany(bean);
@@ -52,6 +58,230 @@ public class ErpPullController {
             ErpCompany bean = BeanUtil.toBean(map, ErpCompany.class);
             this.deleteCompany(bean);
         }
+        //商品分类
+        if("syncProductCategory".equals(type)){
+            ErpProductCategory bean = BeanUtil.toBean(map, ErpProductCategory.class);
+            this.syncProductCategory(bean);
+        }
+        if("deleteProductCategory".equals(type)){
+            ErpProductCategory bean = BeanUtil.toBean(map, ErpProductCategory.class);
+            this.deleteProductCategory(bean);
+        }
+        //人员
+        if("syncPerson".equals(type)){
+            ErpPerson bean = BeanUtil.toBean(map, ErpPerson.class);
+            this.syncPerson(bean);
+        }
+        if("deletePerson".equals(type)){
+            ErpPerson bean = BeanUtil.toBean(map, ErpPerson.class);
+            this.deletePerson(bean);
+        }
+        //仓库
+        if("syncWarehouse".equals(type)){
+            ErpWarehouse bean = BeanUtil.toBean(map, ErpWarehouse.class);
+            this.syncWarehouse(bean);
+        }
+        if("deleteWarehouse".equals(type)){
+            ErpWarehouse bean = BeanUtil.toBean(map, ErpWarehouse.class);
+            this.deleteWarehouse(bean);
+        }
+        //企业规模
+        if("syncCompanyScale".equals(type)){
+            ErpCompanyScale bean = BeanUtil.toBean(map, ErpCompanyScale.class);
+            this.syncCompanyScale(bean);
+        }
+        if("deleteCompanyScale".equals(type)){
+            ErpCompanyScale bean = BeanUtil.toBean(map, ErpCompanyScale.class);
+            this.deleteCompanyScale(bean);
+        }
+        //供应商类别
+        if("syncSupplierType".equals(type)){
+            ErpSupplierType bean = BeanUtil.toBean(map, ErpSupplierType.class);
+            this.syncSupplierType(bean);
+        }
+        if("deleteSupplierType".equals(type)){
+            ErpSupplierType bean = BeanUtil.toBean(map, ErpSupplierType.class);
+            this.deleteSupplierType(bean);
+        }
+        //出库配送信息
+        if("syncSalesDelivery".equals(type)){
+            ErpSalesDelivery bean = BeanUtil.toBean(map, ErpSalesDelivery.class);
+            this.syncSalesDelivery(bean);
+        }
+        //区县主数据
+        if("syncDistrict".equals(type)){
+            ErpDistrict bean = BeanUtil.toBean(map, ErpDistrict.class);
+            this.syncDistrict(bean);
+        }
+        if("deleteDistrict".equals(type)){
+            ErpDistrict bean = BeanUtil.toBean(map, ErpDistrict.class);
+            this.deleteDistrict(bean);
+        }
+        //品牌资料
+        if("syncBrand".equals(type)){
+            ErpBrand bean = BeanUtil.toBean(map, ErpBrand.class);
+            this.syncBrand(bean);
+        }
+        if("deleteBrand".equals(type)){
+            ErpBrand bean = BeanUtil.toBean(map, ErpBrand.class);
+            this.deleteBrand(bean);
+        }
+        //国家地区
+        if("syncCountry".equals(type)){
+            ErpCountry bean = BeanUtil.toBean(map, ErpCountry.class);
+            this.syncCountry(bean);
+        }
+        if("deleteCountry".equals(type)){
+            ErpCountry bean = BeanUtil.toBean(map, ErpCountry.class);
+            this.deleteCountry(bean);
+        }
+        //城市设定
+        if("syncCity".equals(type)){
+            ErpCity bean = BeanUtil.toBean(map, ErpCity.class);
+            this.syncCity(bean);
+        }
+        if("deleteCity".equals(type)){
+            ErpCity bean = BeanUtil.toBean(map, ErpCity.class);
+            this.deleteCity(bean);
+        }
+        //客户等级
+        if("syncCustomerGrade".equals(type)){
+            ErpCustomerGrade bean = BeanUtil.toBean(map, ErpCustomerGrade.class);
+            this.syncCustomerGrade(bean);
+        }
+        if("deleteCustomerGrade".equals(type)){
+            ErpCustomerGrade bean = BeanUtil.toBean(map, ErpCustomerGrade.class);
+            this.deleteCustomerGrade(bean);
+        }
+        //客户类别
+        if("syncCustomerType".equals(type)){
+            ErpCustomerType bean = BeanUtil.toBean(map, ErpCustomerType.class);
+            this.syncCustomerType(bean);
+        }
+        if("deleteCustomerType".equals(type)){
+            ErpCustomerType bean = BeanUtil.toBean(map, ErpCustomerType.class);
+            this.deleteCustomerType(bean);
+        }
+        //客户资料
+        if("syncCustomer".equals(type)){
+            ErpCustomer bean = BeanUtil.toBean(map, ErpCustomer.class);
+            this.syncCustomer(bean);
+        }
+        //岗位设定
+        if("syncPosition".equals(type)){
+            ErpPosition bean = BeanUtil.toBean(map, ErpPosition.class);
+            this.syncPosition(bean);
+        }
+        if("deletePosition".equals(type)){
+            ErpPosition bean = BeanUtil.toBean(map, ErpPosition.class);
+            this.deletePosition(bean);
+        }
+        //快递主数据
+        if("syncExpress".equals(type)){
+            ErpExpress bean = BeanUtil.toBean(map, ErpExpress.class);
+            this.syncExpress(bean);
+        }
+        if("deleteExpress".equals(type)){
+            ErpExpress bean = BeanUtil.toBean(map, ErpExpress.class);
+            this.deleteExpress(bean);
+        }
+        //收入费用设定
+        if("syncIncomeExpenseType".equals(type)){
+            ErpIncomeExpenseType bean = BeanUtil.toBean(map, ErpIncomeExpenseType.class);
+            this.syncIncomeExpenseType(bean);
+        }
+        if("deleteIncomeExpenseType".equals(type)){
+            ErpIncomeExpenseType bean = BeanUtil.toBean(map, ErpIncomeExpenseType.class);
+            this.deleteIncomeExpenseType(bean);
+        }
+        //收入转确认
+        if("confirmIncome".equals(type)){
+            ErpIncomeApplication bean = BeanUtil.toBean(map, ErpIncomeApplication.class);
+            this.confirmIncome(bean);
+        }
+        //省份设定
+        if("syncProvince".equals(type)){
+            ErpProvince bean = BeanUtil.toBean(map, ErpProvince.class);
+            this.syncProvince(bean);
+        }
+        if("deleteProvince".equals(type)){
+            ErpProvince bean = BeanUtil.toBean(map, ErpProvince.class);
+            this.deleteProvince(bean);
+        }
+        //税码设定
+        if("syncTaxCode".equals(type)){
+            ErpTaxCode bean = BeanUtil.toBean(map, ErpTaxCode.class);
+            this.syncTaxCode(bean);
+        }
+        if("deleteTaxCode".equals(type)){
+            ErpTaxCode bean = BeanUtil.toBean(map, ErpTaxCode.class);
+            this.deleteTaxCode(bean);
+        }
+        //行业别
+        if("syncIndustry".equals(type)){
+            ErpIndustry bean = BeanUtil.toBean(map, ErpIndustry.class);
+            this.syncIndustry(bean);
+        }
+        if("deleteIndustry".equals(type)){
+            ErpIndustry bean = BeanUtil.toBean(map, ErpIndustry.class);
+            this.deleteIndustry(bean);
+        }
+        //计量单位设定
+        if("syncUnit".equals(type)){
+            ErpUnit bean = BeanUtil.toBean(map, ErpUnit.class);
+            this.syncUnit(bean);
+        }
+        if("deleteUnit".equals(type)){
+            ErpUnit bean = BeanUtil.toBean(map, ErpUnit.class);
+            this.deleteUnit(bean);
+        }
+        //订单出库状态变更
+        if("updateSalesOrderStatus".equals(type)){
+            ErpSalesOrder bean = BeanUtil.toBean(map, ErpSalesOrder.class);
+            this.updateSalesOrderStatus(bean);
+        }
+        //费用转确认
+        if("confirmExpense".equals(type)){
+            ErpExpense bean = BeanUtil.toBean(map, ErpExpense.class);
+            this.confirmExpense(bean);
+        }
+        //部门设定
+        if("syncDepartment".equals(type)){
+            ErpDepartment bean = BeanUtil.toBean(map, ErpDepartment.class);
+            this.syncDepartment(bean);
+        }
+        if("deleteDepartment".equals(type)){
+            ErpDepartment bean = BeanUtil.toBean(map, ErpDepartment.class);
+            this.deleteDepartment(bean);
+        }
+        //银行设定
+        if("syncBank".equals(type)){
+            ErpBank bean = BeanUtil.toBean(map, ErpBank.class);
+            this.syncBank(bean);
+        }
+        if("deleteBank".equals(type)){
+            ErpBank bean = BeanUtil.toBean(map, ErpBank.class);
+            this.deleteBank(bean);
+        }
+        //销售价格清单
+        if("syncSalesPriceList".equals(type)){
+            ErpSalesPriceList bean = BeanUtil.toBean(map, ErpSalesPriceList.class);
+            this.syncSalesPriceList(bean);
+        }
+        if("syncCustomerSalesPriceList".equals(type)){
+            ErpCustomerSalesPriceList bean = BeanUtil.toBean(map, ErpCustomerSalesPriceList.class);
+            this.syncCustomerSalesPriceList(bean);
+        }
+        if("deleteSalesPriceList".equals(type)){
+            ErpSalesPriceList bean = BeanUtil.toBean(map, ErpSalesPriceList.class);
+            this.deleteSalesPriceList(bean);
+        }
+        //销售退货入库
+        if("syncSalesReturnApplication".equals(type)){
+            ErpSalesReturnApplication bean = BeanUtil.toBean(map, ErpSalesReturnApplication.class);
+            this.syncSalesReturnApplication(bean);
+        }
+
         return R.ok();
     }
 

+ 220 - 11
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductBaseController.java

@@ -1,18 +1,28 @@
 package org.dromara.product.controller;
 
+import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 import cn.hutool.core.lang.tree.Tree;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.RequiredArgsConstructor;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.constraints.*;
 import cn.dev33.satoken.annotation.SaCheckPermission;
-import org.dromara.product.domain.ProductBaseAudit;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.easyes.core.biz.EsPageInfo;
+import org.dromara.easyes.core.conditions.select.LambdaEsQueryWrapper;
+import org.dromara.product.domain.*;
 import org.dromara.product.domain.bo.*;
 import org.dromara.product.domain.vo.*;
+import org.dromara.product.esmapper.ProductEsMapper;
 import org.dromara.product.service.*;
+import org.dromara.system.api.RemoteTaxCodeService;
+import org.dromara.system.api.domain.vo.RemoteTaxCodeVo;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.validation.annotation.Validated;
 import org.dromara.common.idempotent.annotation.RepeatSubmit;
@@ -24,12 +34,31 @@ import org.dromara.common.core.validate.AddGroup;
 import org.dromara.common.core.validate.EditGroup;
 import org.dromara.common.log.enums.BusinessType;
 import org.dromara.common.excel.utils.ExcelUtil;
+import org.dromara.common.excel.core.ExcelResult;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.springframework.http.MediaType;
+import org.springframework.web.multipart.MultipartFile;
+import org.dromara.product.listener.ProductBaseImportListener;
+import org.dromara.product.listener.ProductImageImportListener;
+import org.dromara.product.listener.ProductFullImportListener;
+import org.dromara.product.domain.vo.ProductImageImportVo;
+import org.dromara.product.domain.vo.ProductFullImportVo;
+import org.dromara.product.domain.vo.ProductImageExportVo;
 
 /**
  * 产品基础信息
  * 前端访问路由地址为:/product/base
  *
+ * 商品导入导出接口说明:
+ * 1. /importTemplate - 下载商品基本信息导入模板
+ * 2. /importData - 导入商品基本信息
+ * 3. /importFullTemplate - 下载商品完整信息导入模板(包含图片和详情)
+ * 4. /importFullData - 导入商品完整信息(包含图片和详情)
+ * 5. /importImageTemplate - 下载商品图片详情导入模板
+ * 6. /importImageData - 导入商品图片详情
+ * 7. /export - 导出商品列表(包含图片和详情)
+ * 8. /exportImage - 导出商品图片详情
+ *
  * @author LionLi
  * @date 2025-12-11
  */
@@ -55,6 +84,15 @@ public class ProductBaseController extends BaseController {
 
     private final IProductBaseAuditService productBaseAuditService;
 
+    private final IProductPhotosService productPhotosService;
+
+    private final IProductClassificationDiyService productClassificationDiyService;
+
+    private final ProductEsMapper productEsMapper;
+
+
+    @DubboReference
+    private final RemoteTaxCodeService remoteTaxCodeService;
     /**
      * 查询产品基础信息列表
      */
@@ -72,16 +110,7 @@ public class ProductBaseController extends BaseController {
         return R.ok(productBaseService.getProductStatusCount());
     }
 
-    /**
-     * 导出产品基础信息列表
-     */
-    //@SaCheckPermission("product:base:export")
-    @Log(title = "产品基础信息", businessType = BusinessType.EXPORT)
-    @PostMapping("/export")
-    public void export(ProductBaseBo bo, HttpServletResponse response) {
-        List<ProductBaseVo> list = productBaseService.queryList(bo);
-        ExcelUtil.exportExcel(list, "产品基础信息", ProductBaseVo.class, response);
-    }
+
 
     /**
      * 获取产品基础信息详细信息
@@ -316,5 +345,185 @@ public class ProductBaseController extends BaseController {
         return R.ok(isDuplicate);
     }
 
+    /**
+     * 导出产品基础信息列表
+     */
+    //@SaCheckPermission("product:base:export")
+    @Log(title = "产品基础信息", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(@RequestBody ProductBaseBo bo, HttpServletResponse response) {
+
+        LambdaEsQueryWrapper<ProductBaseVo> productBaseVoLambdaEsQueryWrapper = new LambdaEsQueryWrapper<>();
+        productBaseVoLambdaEsQueryWrapper.ge(ObjectUtil.isNotEmpty(bo.getProductNo()), ProductBaseVo::getProductNo, bo.getProductNo());
+        productBaseVoLambdaEsQueryWrapper.orderByAsc(ProductBaseVo::getProductNo);
+        //获取全部税收编码
+        List<RemoteTaxCodeVo> remoteTaxCodeVos = remoteTaxCodeService.selectAll(new RemoteTaxCodeVo());
+        Map<Long, String> remoteTaxCodeMap = remoteTaxCodeVos.stream().filter(item -> ObjectUtil.isNotEmpty(item.getTaxationNo())).collect(Collectors.toMap(RemoteTaxCodeVo::getId, RemoteTaxCodeVo::getTaxationNo));
+        //获取品牌
+        List<ProductBrand> productBrands = productBrandService.list(Wrappers.<ProductBrand>lambdaQuery());
+        Map<Long, String> productBrandMap = productBrands.stream().filter(item -> ObjectUtil.isNotEmpty(item.getBrandNo())).collect(Collectors.toMap(ProductBrand::getId, ProductBrand::getBrandNo));
+        //获取分类
+        List<ProductCategory> productCategories = productCategoryService.list(Wrappers.<ProductCategory>lambdaQuery());
+        Map<Long, String> productCategoryMap = productCategories.stream().filter(item -> ObjectUtil.isNotEmpty(item.getCategoryNo())).collect(Collectors.toMap(ProductCategory::getId, ProductCategory::getCategoryNo));
+        //获取单位
+        List<ProductUnit> productUnits = productUnitService.list(Wrappers.<ProductUnit>lambdaQuery());
+        Map<Long, String> productUnitMap = productUnits.stream().filter(item -> ObjectUtil.isNotEmpty(item.getUnitNo())).collect(Collectors.toMap(ProductUnit::getId, ProductUnit::getUnitNo));
+        EsPageInfo<ProductBaseVo> esPageInfo = productEsMapper.pageQuery(productBaseVoLambdaEsQueryWrapper, 1, 1000);
+
+        List<ProductBaseVo> list = esPageInfo.getList();
+        // 转换为导入Vo格式以便导出(包含图片和详情)
+        List<ProductFullImportVo> exportList = list.stream().map(vo -> {
+            ProductFullImportVo exportVo = new ProductFullImportVo();
+            exportVo.setProductNo(vo.getProductNo());
+            exportVo.setProductName(vo.getItemName());
+            exportVo.setCategoryNo(productCategoryMap.get(vo.getBottomCategoryId()));
+            exportVo.setCategoryName(vo.getBottomCategoryName());
+            exportVo.setSpecificationsCode(vo.getSpecificationsCode());
+            exportVo.setProductDescription(vo.getProductDescription());
+            exportVo.setPromotionTitle(vo.getPromotionTitle());
+            exportVo.setProductExplain(vo.getProductExplain());
+            exportVo.setBarCoding(vo.getBarCoding());
+            exportVo.setInvoiceName(vo.getInvoiceName());
+            exportVo.setInvoiceSpecs(vo.getInvoiceSpecs());
+            exportVo.setTaxRate(vo.getTaxRate());
+            exportVo.setIsTaxIncluded("是");
+            exportVo.setBrandNo(productBrandMap.get(vo.getBrandId()));
+            exportVo.setBrandName(vo.getBrandName());
+            exportVo.setUnitNo(ObjectUtil.isNotEmpty(vo.getUnitId()) ? productUnitMap.get(Long.valueOf(vo.getUnitId())) : "");
+            exportVo.setUnitName(vo.getUnitName());
+            exportVo.setReferenceLink(vo.getReferenceLink());
+            exportVo.setMarketPrice(vo.getMarketPrice());
+            exportVo.setMemberPrice(vo.getMemberPrice());
+            exportVo.setMinSellingPrice(vo.getMinSellingPrice());
+            exportVo.setPurchasingPrice(vo.getPurchasingPrice());
+            exportVo.setMaxPurchasePrice(vo.getMaxPurchasePrice());
+            exportVo.setShelfStatus(vo.getProductStatus() != null && vo.getProductStatus() == 1 ? "1" : "0");
+            exportVo.setMainImage(vo.getProductImage());
+            exportVo.setTaxCode(remoteTaxCodeMap.get(vo.getTaxationId()));
+            //获取自定义属性
+            ProductClassificationDiy one = productClassificationDiyService.getOne(Wrappers.<ProductClassificationDiy>lambdaQuery()
+                .eq(ProductClassificationDiy::getProductId, vo.getId())
+                .orderByDesc(ProductClassificationDiy::getCreateTime)
+                .last("limit 1")
+            );
+            if(ObjectUtil.isNotEmpty(one)){
+                exportVo.setCustomAttributeName(one.getAttributeKey());
+                exportVo.setCustomAttributeValue(one.getAttributeValue());
+            }
+            return exportVo;
+        }).toList();
+        ExcelUtil.exportExcel(exportList, "产品基础信息", ProductFullImportVo.class, response);
+    }
+
+    /**
+     * 获取导入模板(包含基本信息)
+     */
+    //@SaCheckPermission("product:base:import")
+    @PostMapping("/importTemplate")
+    public void importTemplate(HttpServletResponse response) {
+        ExcelUtil.exportExcel(new ArrayList<>(), "商品导入模板", ProductBaseImportVo.class, response);
+    }
+
+    /**
+     * 获取商品完整信息导入模板(包含图片和详情)
+     */
+    //@SaCheckPermission("product:base:import")
+    @PostMapping("/importFullTemplate")
+    public void importFullTemplate(HttpServletResponse response) {
+        ExcelUtil.exportExcel(new ArrayList<>(), "商品完整信息导入模板", ProductFullImportVo.class, response);
+    }
+
+    /**
+     * 导入商品基础信息
+     */
+    //@SaCheckPermission("product:base:import")
+    @Log(title = "商品基础信息", businessType = BusinessType.IMPORT)
+    @PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public R<Void> importData(@RequestPart("file") MultipartFile file) throws Exception {
+        ExcelResult<ProductBaseImportVo> result = ExcelUtil.importExcel(
+            file.getInputStream(),
+            ProductBaseImportVo.class,
+            new ProductBaseImportListener(productBaseService, remoteTaxCodeService)
+        );
+        return R.ok(result.getAnalysis());
+    }
+
+    /**
+     * 导入商品完整信息(包含图片和详情)
+     */
+    //@SaCheckPermission("product:base:import")
+    @Log(title = "商品完整信息", businessType = BusinessType.IMPORT)
+    @PostMapping(value = "/importFullData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public R<Void> importFullData(@RequestPart("file") MultipartFile file) throws Exception {
+        ExcelResult<ProductFullImportVo> result = ExcelUtil.importExcel(
+            file.getInputStream(),
+            ProductFullImportVo.class,
+            new ProductFullImportListener(productBaseService, remoteTaxCodeService)
+        );
+        return R.ok(result.getAnalysis());
+    }
+
+    /**
+     * 获取商品图片详情导入模板
+     */
+    //@SaCheckPermission("product:base:import")
+    @PostMapping("/importImageTemplate")
+    public void importImageTemplate(HttpServletResponse response) {
+        ExcelUtil.exportExcel(new ArrayList<>(), "商品图片详情导入模板", ProductImageImportVo.class, response);
+    }
+
+    /**
+     * 导入商品图片详情
+     */
+    //@SaCheckPermission("product:base:import")
+    @Log(title = "商品图片详情", businessType = BusinessType.IMPORT)
+    @PostMapping(value = "/importImageData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+    public R<Void> importImageData(@RequestPart("file") MultipartFile file) throws Exception {
+        ExcelResult<ProductImageImportVo> result = ExcelUtil.importExcel(
+            file.getInputStream(),
+            ProductImageImportVo.class,
+            new ProductImageImportListener(productBaseService)
+        );
+        return R.ok(result.getAnalysis());
+    }
+
+    /**
+     * 导出商品图片详情
+     */
+    //@SaCheckPermission("product:base:export")
+    @Log(title = "商品图片详情", businessType = BusinessType.EXPORT)
+    @PostMapping("/exportImage")
+    public void exportImage(@RequestBody ProductBaseBo bo, HttpServletResponse response) {
+        LambdaEsQueryWrapper<ProductBaseVo> productBaseVoLambdaEsQueryWrapper = new LambdaEsQueryWrapper<>();
+        productBaseVoLambdaEsQueryWrapper.ge(ObjectUtil.isNotEmpty(bo.getProductNo()), ProductBaseVo::getProductNo, bo.getProductNo());
+        productBaseVoLambdaEsQueryWrapper.orderByAsc(ProductBaseVo::getProductNo);
+        EsPageInfo<ProductBaseVo> esPageInfo = productEsMapper.pageQuery(productBaseVoLambdaEsQueryWrapper, 1, 1000);
+        List<ProductBaseVo> list = esPageInfo.getList();
+        List<ProductImageExportVo> exportList = list.stream().map(vo -> {
+            ProductImageExportVo exportVo = new ProductImageExportVo();
+            exportVo.setProductNo(vo.getProductNo());
+            exportVo.setMainImage(vo.getProductImage());
+            if(ObjectUtil.isNotEmpty(vo.getImageUrl())||ObjectUtil.isNotEmpty(vo.getPcDetail())){
+                ProductPhotosVo productPhotosVo = productPhotosService.queryById(vo.getId());
+                if(ObjectUtil.isNotEmpty(productPhotosVo)){
+                    exportVo.setProductDetail(productPhotosVo.getProductDetailsPc());
+                    exportVo.setCarouselImages(productPhotosVo.getImageUrl());
+                }
+            }
+            return exportVo;
+        }).toList();
+        ExcelUtil.exportExcel(exportList, "商品图片详情", ProductImageExportVo.class, response);
+    }
+
+    /**
+     * 导出商品完整信息(包含图片和详情)
+     */
+    //@SaCheckPermission("product:base:export")
+    @Log(title = "商品完整信息", businessType = BusinessType.EXPORT)
+    @PostMapping("/exportFull")
+    public void exportFull(@RequestBody ProductBaseBo bo, HttpServletResponse response) {
+        export(bo, response);
+    }
+
 
 }

+ 1 - 1
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductBaseBo.java

@@ -312,7 +312,7 @@ public class ProductBaseBo extends BaseEntity {
     private Date supplyValidityPeriod;
 
     /**
-     * 是否包邮 0 不包邮,1包邮
+     * 是否一件代发 0 不包邮,1包邮
      */
     private Integer supplyPostStatus;
     /**

+ 239 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductBaseImportVo.java

@@ -0,0 +1,239 @@
+package org.dromara.product.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+import org.dromara.common.excel.annotation.ExcelRequired;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 商品基础信息导入视图对象
+ *
+ * @author LionLi
+ * @date 2026-06-05
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class ProductBaseImportVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 序号
+     */
+    @ExcelProperty(value = "序号", index = 0)
+    private Integer serialNumber;
+
+    /**
+     * 产品编号
+     */
+    @ExcelProperty(value = "产品编号", index = 1)
+    @ExcelRequired
+    private String productNo;
+
+    /**
+     * 产品名称
+     */
+    @ExcelProperty(value = "产品名称", index = 2)
+    @ExcelRequired
+    private String productName;
+
+    /**
+     * 产品分类编号
+     */
+    @ExcelProperty(value = "产品分类编号", index = 3)
+    private String categoryNo;
+
+    /**
+     * 产品分类名称
+     */
+    @ExcelProperty(value = "产品分类名称", index = 4)
+    private String categoryName;
+
+    /**
+     * 规格型号
+     */
+    @ExcelProperty(value = "规格型号", index = 5)
+    private String specificationsCode;
+
+    /**
+     * 产品描述
+     */
+    @ExcelProperty(value = "产品描述", index = 6)
+    private String productDescription;
+
+    /**
+     * 促销标题
+     */
+    @ExcelProperty(value = "促销标题", index = 7)
+    private String promotionTitle;
+
+    /**
+     * 商品说明
+     */
+    @ExcelProperty(value = "商品说明", index = 8)
+    private String productExplain;
+
+    /**
+     * UPC(69)条码
+     */
+    @ExcelProperty(value = "UPC(69)条码", index = 9)
+    private String barCoding;
+
+    /**
+     * 发票名称
+     */
+    @ExcelProperty(value = "发票名称", index = 10)
+    private String invoiceName;
+
+    /**
+     * 发票规格
+     */
+    @ExcelProperty(value = "发票规格", index = 11)
+    private String invoiceSpecs;
+
+    /**
+     * 税收编码
+     */
+    @ExcelProperty(value = "税收编码", index = 12)
+    private String taxCode;
+
+    /**
+     * 是否含税
+     */
+    @ExcelProperty(value = "是否含税", index = 13)
+    private String isTaxIncluded;
+
+    /**
+     * 税率
+     */
+    @ExcelProperty(value = "税率", index = 14)
+    private BigDecimal taxRate;
+
+    /**
+     * 品牌编号
+     */
+    @ExcelProperty(value = "品牌编号", index = 15)
+    private String brandNo;
+
+    /**
+     * 品牌资料名称
+     */
+    @ExcelProperty(value = "品牌资料名称", index = 16)
+    private String brandName;
+
+    /**
+     * 单位名称
+     */
+    @ExcelProperty(value = "单位名称", index = 17)
+    private String unitName;
+
+    /**
+     * 单位编号
+     */
+    @ExcelProperty(value = "单位编号", index = 18)
+    private String unitNo;
+
+    /**
+     * 参考链接
+     */
+    @ExcelProperty(value = "参考链接", index = 19)
+    private String referenceLink;
+
+    /**
+     * 自定义属性名称
+     */
+    @ExcelProperty(value = "自定义属性名称", index = 20)
+    private String customAttributeName;
+
+    /**
+     * 自定义属性值
+     */
+    @ExcelProperty(value = "自定义属性值", index = 21)
+    private String customAttributeValue;
+
+    /**
+     * 市场价
+     */
+    @ExcelProperty(value = "市场价", index = 22)
+    @ExcelRequired
+    private BigDecimal marketPrice;
+
+    /**
+     * 官网价
+     */
+    @ExcelProperty(value = "官网价", index = 23)
+    private BigDecimal officialPrice;
+
+    /**
+     * 最低售价
+     */
+    @ExcelProperty(value = "最低售价", index = 24)
+    @ExcelRequired
+    private BigDecimal minSellingPrice;
+
+    /**
+     * 起订量
+     */
+    @ExcelProperty(value = "起订量", index = 25)
+    private Long minOrderQuantity;
+
+    /**
+     * 采购价
+     */
+    @ExcelProperty(value = "采购价", index = 26)
+    @ExcelRequired
+    private BigDecimal purchasingPrice;
+
+    /**
+     * 最高采购价
+     */
+    @ExcelProperty(value = "最高采购价", index = 27)
+    private BigDecimal maxPurchasePrice;
+
+    /**
+     * 上下架状态
+     */
+    @ExcelProperty(value = "上下架状态", index = 28)
+    private String shelfStatus;
+
+    /**
+     * 主图
+     */
+    @ExcelProperty(value = "主图", index = 29)
+    private String mainImage;
+
+    /**
+     * 轮播图
+     */
+    @ExcelProperty(value = "轮播图", index = 30)
+    private String carouselImages;
+
+    /**
+     * 商品详情
+     */
+    @ExcelProperty(value = "商品详情", index = 31)
+    private String productDetail;
+
+    /**
+     * 售后服务
+     */
+    @ExcelProperty(value = "售后服务", index = 32)
+    private String afterSalesService;
+
+    /**
+     * 服务保障
+     */
+    @ExcelProperty(value = "服务保障", index = 33)
+    private String serviceGuarantee;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注", index = 34)
+    private String remark;
+}

+ 211 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductFullImportVo.java

@@ -0,0 +1,211 @@
+package org.dromara.product.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+import org.dromara.common.excel.annotation.ExcelRequired;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 商品完整导入视图对象(包含基本信息和图片详情)
+ *
+ * @author LionLi
+ * @date 2026-06-05
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class ProductFullImportVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 序号
+     */
+    @ExcelProperty(value = "序号", index = 0)
+    private Integer serialNumber;
+
+    /**
+     * 产品编号
+     */
+    @ExcelProperty(value = "产品编号", index = 1)
+    @ExcelRequired
+    private String productNo;
+
+    /**
+     * 产品名称
+     */
+    @ExcelProperty(value = "产品名称", index = 2)
+    @ExcelRequired
+    private String productName;
+
+    /**
+     * 产品分类编号
+     */
+    @ExcelProperty(value = "产品分类编号", index = 3)
+    private String categoryNo;
+
+    /**
+     * 产品分类名称
+     */
+    @ExcelProperty(value = "产品分类名称", index = 4)
+    private String categoryName;
+
+    /**
+     * 规格型号
+     */
+    @ExcelProperty(value = "规格型号", index = 5)
+    private String specificationsCode;
+
+    /**
+     * 产品描述
+     */
+    @ExcelProperty(value = "产品描述", index = 6)
+    private String productDescription;
+
+    /**
+     * 促销标题
+     */
+    @ExcelProperty(value = "促销标题", index = 7)
+    private String promotionTitle;
+
+    /**
+     * 商品说明
+     */
+    @ExcelProperty(value = "商品说明", index = 8)
+    private String productExplain;
+
+    /**
+     * UPC(69)条码
+     */
+    @ExcelProperty(value = "UPC(69)条码", index = 9)
+    private String barCoding;
+
+    /**
+     * 发票名称
+     */
+    @ExcelProperty(value = "发票名称", index = 10)
+    private String invoiceName;
+
+    /**
+     * 发票规格
+     */
+    @ExcelProperty(value = "发票规格", index = 11)
+    private String invoiceSpecs;
+
+    /**
+     * 税收编码
+     */
+    @ExcelProperty(value = "税收编码", index = 12)
+    private String taxCode;
+
+    /**
+     * 是否含税
+     */
+    @ExcelProperty(value = "是否含税", index = 13)
+    private String isTaxIncluded;
+
+    /**
+     * 税率
+     */
+    @ExcelProperty(value = "税率", index = 14)
+    private BigDecimal taxRate;
+
+    /**
+     * 品牌编号
+     */
+    @ExcelProperty(value = "品牌编号", index = 15)
+    private String brandNo;
+
+    /**
+     * 品牌资料名称
+     */
+    @ExcelProperty(value = "品牌资料名称", index = 16)
+    private String brandName;
+
+    /**
+     * 单位名称
+     */
+    @ExcelProperty(value = "单位名称", index = 17)
+    private String unitName;
+
+    /**
+     * 单位编号
+     */
+    @ExcelProperty(value = "单位编号", index = 18)
+    private String unitNo;
+
+    /**
+     * 参考链接
+     */
+    @ExcelProperty(value = "参考链接", index = 19)
+    private String referenceLink;
+
+    /**
+     * 自定义属性名称
+     */
+    @ExcelProperty(value = "自定义属性名称", index = 20)
+    private String customAttributeName;
+
+    /**
+     * 自定义属性值
+     */
+    @ExcelProperty(value = "自定义属性值", index = 21)
+    private String customAttributeValue;
+
+    /**
+     * 市场价
+     */
+    @ExcelProperty(value = "市场价", index = 22)
+    @ExcelRequired
+    private BigDecimal marketPrice;
+
+    /**
+     * 官网价
+     */
+    @ExcelProperty(value = "官网价", index = 23)
+    private BigDecimal memberPrice;
+
+    /**
+     * 最低售价
+     */
+    @ExcelProperty(value = "最低售价", index = 24)
+    @ExcelRequired
+    private BigDecimal minSellingPrice;
+
+    /**
+     * 起订量
+     */
+    @ExcelProperty(value = "起订量", index = 25)
+    private Long minOrderQuantity;
+
+    /**
+     * 采购价
+     */
+    @ExcelProperty(value = "采购价", index = 26)
+    @ExcelRequired
+    private BigDecimal purchasingPrice;
+
+    /**
+     * 最高采购价
+     */
+    @ExcelProperty(value = "最高采购价", index = 27)
+    private BigDecimal maxPurchasePrice;
+
+    /**
+     * 上下架状态
+     */
+    @ExcelProperty(value = "上下架状态", index = 28)
+    private String shelfStatus;
+
+    /**
+     * 主图
+     */
+    @ExcelProperty(value = "主图", index = 29)
+    private String mainImage;
+
+
+}

+ 46 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductImageExportVo.java

@@ -0,0 +1,46 @@
+package org.dromara.product.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 商品图片详情导出视图对象
+ *
+ * @author LionLi
+ * @date 2026-06-05
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class ProductImageExportVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 商品编号
+     */
+    @ExcelProperty(value = "商品编号", index = 0)
+    private String productNo;
+
+    /**
+     * 主图
+     */
+    @ExcelProperty(value = "主图", index = 1)
+    private String mainImage;
+
+    /**
+     * 轮播图(多张图片用逗号分隔)
+     */
+    @ExcelProperty(value = "轮播图", index = 2)
+    private String carouselImages;
+
+    /**
+     * 商品详情(HTML格式)
+     */
+    @ExcelProperty(value = "商品详情", index = 3)
+    private String productDetail;
+}

+ 54 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductImageImportVo.java

@@ -0,0 +1,54 @@
+package org.dromara.product.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.Data;
+import org.dromara.common.excel.annotation.ExcelRequired;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 商品图片详情导入视图对象
+ *
+ * @author LionLi
+ * @date 2026-06-05
+ */
+@Data
+@ExcelIgnoreUnannotated
+public class ProductImageImportVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 序号
+     */
+    @ExcelProperty(value = "序号", index = 0)
+    private Integer serialNumber;
+
+    /**
+     * 商品编号
+     */
+    @ExcelProperty(value = "商品编号", index = 1)
+    @ExcelRequired
+    private String productNo;
+
+    /**
+     * 主图
+     */
+    @ExcelProperty(value = "主图", index = 2)
+    private String mainImage;
+
+    /**
+     * 轮播图(多张图片用逗号分隔)
+     */
+    @ExcelProperty(value = "轮播图", index = 3)
+    private String carouselImages;
+
+    /**
+     * 商品详情(HTML格式)
+     */
+    @ExcelProperty(value = "商品详情", index = 4)
+    private String productDetail;
+}

+ 288 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/listener/ProductBaseImportListener.java

@@ -0,0 +1,288 @@
+package org.dromara.product.listener;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.idev.excel.context.AnalysisContext;
+import cn.idev.excel.event.AnalysisEventListener;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.core.utils.ValidatorUtils;
+import org.dromara.common.excel.core.ExcelListener;
+import org.dromara.common.excel.core.ExcelResult;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.product.domain.ProductBase;
+import org.dromara.product.domain.ProductBrand;
+import org.dromara.product.domain.ProductCategory;
+import org.dromara.product.domain.ProductUnit;
+import org.dromara.product.domain.bo.ProductBaseBo;
+import org.dromara.product.domain.vo.ProductBaseImportVo;
+import org.dromara.product.domain.vo.ProductCategoryVo;
+import org.dromara.product.domain.vo.ProductBrandVo;
+import org.dromara.product.domain.vo.ProductUnitVo;
+import org.dromara.product.service.IProductBaseService;
+import org.dromara.product.service.IProductCategoryService;
+import org.dromara.product.service.IProductBrandService;
+import org.dromara.product.service.IProductUnitService;
+import org.dromara.system.api.RemoteTaxCodeService;
+import org.dromara.system.api.domain.vo.RemoteTaxCodeVo;
+
+import java.util.List;
+
+/**
+ * 商品基础信息导入监听器
+ *
+ * @author LionLi
+ * @date 2026-06-05
+ */
+@Slf4j
+public class ProductBaseImportListener extends AnalysisEventListener<ProductBaseImportVo> implements ExcelListener<ProductBaseImportVo> {
+
+    private final IProductBaseService productBaseService;
+    private final IProductCategoryService productCategoryService;
+    private final IProductBrandService productBrandService;
+    private final IProductUnitService productUnitService;
+
+    private final RemoteTaxCodeService remoteTaxCodeService;
+
+    private int successNum = 0;
+    private int failureNum = 0;
+    private final StringBuilder successMsg = new StringBuilder();
+    private final StringBuilder failureMsg = new StringBuilder();
+
+    public ProductBaseImportListener(RemoteTaxCodeService remoteTaxCodeService) {
+        this.remoteTaxCodeService = remoteTaxCodeService;
+        this.productBaseService = null;
+        this.productCategoryService = null;
+        this.productBrandService = null;
+        this.productUnitService = null;
+    }
+
+    public ProductBaseImportListener(IProductBaseService productBaseService, RemoteTaxCodeService remoteTaxCodeService) {
+        this.productBaseService = productBaseService;
+        this.remoteTaxCodeService = remoteTaxCodeService;
+        this.productCategoryService = SpringUtils.getBean(IProductCategoryService.class);
+        this.productBrandService = SpringUtils.getBean(IProductBrandService.class);
+        this.productUnitService = SpringUtils.getBean(IProductUnitService.class);
+    }
+
+    @Override
+    public void invoke(ProductBaseImportVo importVo, AnalysisContext context) {
+        try {
+            int rowNum = context.readRowHolder().getRowIndex() + 1;
+
+            // 校验必填字段
+            if (ObjectUtil.isEmpty(importVo.getProductNo())) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,产品编号不能为空");
+                return;
+            }
+
+            if (ObjectUtil.isEmpty(importVo.getProductName())) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,产品名称不能为空");
+                return;
+            }
+
+            if (ObjectUtil.isEmpty(importVo.getMarketPrice())) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,市场价不能为空");
+                return;
+            }
+
+            if (ObjectUtil.isEmpty(importVo.getMinSellingPrice())) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,最低售价不能为空");
+                return;
+            }
+
+            if (ObjectUtil.isEmpty(importVo.getPurchasingPrice())) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,采购价不能为空");
+                return;
+            }
+
+            // 检查产品编号是否已存在
+            List<ProductBase> existingList = productBaseService.list(Wrappers.<ProductBase>lambdaQuery()
+                .eq(ProductBase::getProductNo, importVo.getProductNo()));
+
+            if (ObjectUtil.isNotEmpty(existingList)) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,产品编号 ").append(importVo.getProductNo()).append(" 已存在");
+                return;
+            }
+
+            // 构建产品基础信息Bo
+            ProductBaseBo productBo = new ProductBaseBo();
+            productBo.setProductNo(importVo.getProductNo());
+            productBo.setItemName(importVo.getProductName());
+            productBo.setSpecificationsCode(importVo.getSpecificationsCode());
+            productBo.setProductDescription(importVo.getProductDescription());
+            productBo.setPromotionTitle(importVo.getPromotionTitle());
+            productBo.setProductExplain(importVo.getProductExplain());
+            productBo.setBarCoding(importVo.getBarCoding());
+            productBo.setInvoiceName(importVo.getInvoiceName());
+            productBo.setInvoiceSpecs(importVo.getInvoiceSpecs());
+
+            productBo.setTaxRate(importVo.getTaxRate());
+            productBo.setReferenceLink(importVo.getReferenceLink());
+            productBo.setMarketPrice(importVo.getMarketPrice());
+            productBo.setMinSellingPrice(importVo.getMinSellingPrice());
+            productBo.setPurchasingPrice(importVo.getPurchasingPrice());
+            productBo.setMaxPurchasePrice(importVo.getMaxPurchasePrice());
+            productBo.setAfterSalesService(importVo.getAfterSalesService());
+            productBo.setServiceGuarantee(importVo.getServiceGuarantee());
+            productBo.setRemark(importVo.getRemark());
+
+            // 处理分类信息
+            if (ObjectUtil.isNotEmpty(importVo.getCategoryNo())) {
+                // 根据分类编号查询分类ID
+                ProductCategory bottomCategory = productCategoryService.getOne(Wrappers.lambdaQuery(ProductCategory.class).eq(ProductCategory::getCategoryNo, importVo.getCategoryNo()));
+                if (ObjectUtil.isNotEmpty(bottomCategory)) {
+                    ProductCategory mediumCategory = productCategoryService.getById(bottomCategory.getParentId());
+                    // 根据分类层级设置对应的分类ID
+                    ProductCategory topCategory = productCategoryService.getById(mediumCategory.getParentId());
+                    productBo.setTopCategoryId(topCategory.getId());
+                    productBo.setMediumCategoryId(mediumCategory.getId());
+                    productBo.setBottomCategoryId(bottomCategory.getId());
+                } else {
+                    failureNum++;
+                    failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,产品分类编号 ").append(importVo.getCategoryNo()).append(" 不存在");
+                    return;
+                }
+            }
+
+            // 处理品牌信息
+            if (ObjectUtil.isNotEmpty(importVo.getBrandNo())) {
+                // 根据品牌编号查询品牌ID
+                ProductBrand productBrand = productBrandService.getOne(Wrappers.lambdaQuery(ProductBrand.class).eq(ProductBrand::getBrandNo, importVo.getBrandNo()));
+                if (ObjectUtil.isNotEmpty(productBrand)) {
+                    productBo.setBrandId(productBrand.getId());
+                } else {
+                    failureNum++;
+                    failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,品牌编号 ").append(importVo.getBrandNo()).append(" 不存在");
+                    return;
+                }
+            }
+
+            // 处理单位信息
+            if (ObjectUtil.isNotEmpty(importVo.getUnitNo())) {
+                // 根据单位编号查询单位ID
+                ProductUnit productUnit = productUnitService.getOne(Wrappers.lambdaQuery(ProductUnit.class).eq(ProductUnit::getUnitNo, importVo.getUnitNo()));
+                if (ObjectUtil.isNotEmpty(productUnit)) {
+                    productBo.setUnitId(String.valueOf(productUnit.getId()));
+                } else {
+                    failureNum++;
+                    failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,单位编号 ").append(importVo.getUnitNo()).append(" 不存在");
+                    return;
+                }
+            }
+
+            // 通过税率编码获取税率ID
+            if (ObjectUtil.isNotEmpty(importVo.getTaxCode())) {
+                try {
+                    RemoteTaxCodeVo taxCodeVo = remoteTaxCodeService.selectByTaxCodeNo(importVo.getTaxCode());
+                    if (taxCodeVo != null) {
+                        productBo.setTaxationId(taxCodeVo.getId());
+                        // 如果Excel中没有填写税率,使用税收编码中的税率
+                        if (ObjectUtil.isEmpty(importVo.getTaxRate()) && taxCodeVo.getTaxrate() != null) {
+                            productBo.setTaxRate(taxCodeVo.getTaxrate());
+                        }
+                    } else {
+                        failureNum++;
+                        failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,税收编码 ").append(importVo.getTaxCode()).append(" 不存在");
+                        return;
+                    }
+                } catch (Exception e) {
+                    failureNum++;
+                    failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,税收编码查询失败:").append(e.getMessage());
+                    log.error("税收编码查询失败", e);
+                    return;
+                }
+            }
+
+
+            // 处理上下架状态
+            if (ObjectUtil.isNotEmpty(importVo.getShelfStatus())) {
+                Integer status = "1".equals(importVo.getShelfStatus()) ? 1 : 0;
+                productBo.setProductStatus(status);
+            }
+
+            // 处理起订量
+            if (ObjectUtil.isNotEmpty(importVo.getMinOrderQuantity())) {
+                productBo.setMinOrderQuantity(importVo.getMinOrderQuantity());
+            }
+
+            // 处理主图
+            if (ObjectUtil.isNotEmpty(importVo.getMainImage())) {
+                productBo.setProductImage(importVo.getMainImage());
+            }
+
+            // 处理轮播图(多图)
+            if (ObjectUtil.isNotEmpty(importVo.getCarouselImages())) {
+                productBo.setImageUrl(importVo.getCarouselImages());
+            }
+
+            // 处理商品详情(PC端)
+            if (ObjectUtil.isNotEmpty(importVo.getProductDetail())) {
+                productBo.setPcDetail(importVo.getProductDetail());
+                productBo.setMobileDetail(importVo.getProductDetail());
+            }
+
+
+
+            // 调用Service进行新增
+            ValidatorUtils.validate(productBo);
+            productBo.setCreateBy(LoginHelper.getUserId());
+            productBaseService.insertByBo(productBo);
+
+            successNum++;
+            successMsg.append("<br/>").append(successNum).append("、产品编号 ").append(importVo.getProductNo()).append(" 导入成功");
+
+        } catch (Exception e) {
+            failureNum++;
+            String msg = "<br/>" + failureNum + "、产品编号 " + importVo.getProductNo() + " 导入失败:";
+            String message = e.getMessage();
+            if (e instanceof ConstraintViolationException cvException) {
+                message = StreamUtils.join(cvException.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
+            }
+            failureMsg.append(msg).append(message);
+            log.error(msg, e);
+        }
+    }
+
+    @Override
+    public void doAfterAllAnalysed(AnalysisContext context) {
+        // 所有数据解析完成后调用
+    }
+
+    @Override
+    public ExcelResult<ProductBaseImportVo> getExcelResult() {
+        return new ExcelResult<ProductBaseImportVo>() {
+
+            @Override
+            public String getAnalysis() {
+                if (failureNum > 0) {
+                    failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
+                    throw new ServiceException(failureMsg.toString());
+                } else {
+                    successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
+                }
+                return successMsg.toString();
+            }
+
+            @Override
+            public List<ProductBaseImportVo> getList() {
+                return null;
+            }
+
+            @Override
+            public List<String> getErrorList() {
+                return null;
+            }
+        };
+    }
+}

+ 269 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/listener/ProductFullImportListener.java

@@ -0,0 +1,269 @@
+package org.dromara.product.listener;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.idev.excel.context.AnalysisContext;
+import cn.idev.excel.event.AnalysisEventListener;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import jakarta.validation.ConstraintViolation;
+import jakarta.validation.ConstraintViolationException;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.core.utils.StreamUtils;
+import org.dromara.common.excel.core.ExcelListener;
+import org.dromara.common.excel.core.ExcelResult;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.product.domain.*;
+import org.dromara.product.domain.bo.ProductBaseBo;
+import org.dromara.product.domain.vo.ProductFullImportVo;
+import org.dromara.product.service.*;
+import org.dromara.system.api.RemoteTaxCodeService;
+import org.dromara.system.api.domain.vo.RemoteTaxCodeVo;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * 商品完整信息导入监听器(包含图片和详情)
+ *
+ * @author LionLi
+ * @date 2026-06-05
+ */
+@Slf4j
+public class ProductFullImportListener extends AnalysisEventListener<ProductFullImportVo> implements ExcelListener<ProductFullImportVo> {
+
+    private final IProductBaseService productBaseService;
+    private final IProductCategoryService productCategoryService;
+    private final IProductBrandService productBrandService;
+    private final IProductUnitService productUnitService;
+    private final RemoteTaxCodeService remoteTaxCodeService;
+    private final IProductClassificationDiyService productClassificationDiyService;
+
+    private int successNum = 0;
+    private int failureNum = 0;
+    private final StringBuilder successMsg = new StringBuilder();
+    private final StringBuilder failureMsg = new StringBuilder();
+
+    public ProductFullImportListener(IProductBaseService productBaseService, RemoteTaxCodeService remoteTaxCodeService) {
+        this.productBaseService = productBaseService;
+        this.remoteTaxCodeService = remoteTaxCodeService;
+        this.productCategoryService = SpringUtils.getBean(IProductCategoryService.class);
+        this.productBrandService = SpringUtils.getBean(IProductBrandService.class);
+        this.productUnitService = SpringUtils.getBean(IProductUnitService.class);
+        this.productClassificationDiyService = SpringUtils.getBean(IProductClassificationDiyService.class);
+    }
+
+    @Override
+    public void invoke(ProductFullImportVo importVo, AnalysisContext context) {
+        try {
+            int rowNum = context.readRowHolder().getRowIndex() + 1;
+
+            // 校验必填字段
+            if (ObjectUtil.isEmpty(importVo.getProductNo())) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,产品编号不能为空");
+                return;
+            }
+
+            if (ObjectUtil.isEmpty(importVo.getProductName())) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,产品名称不能为空");
+                return;
+            }
+
+            if (ObjectUtil.isEmpty(importVo.getMarketPrice())) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,市场价不能为空");
+                return;
+            }
+
+            if (ObjectUtil.isEmpty(importVo.getMinSellingPrice())) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,最低售价不能为空");
+                return;
+            }
+
+            if (ObjectUtil.isEmpty(importVo.getPurchasingPrice())) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,采购价不能为空");
+                return;
+            }
+
+            // 检查产品编号是否已存在
+            List<ProductBase> existingList = productBaseService.list(Wrappers.<ProductBase>lambdaQuery()
+                .eq(ProductBase::getProductNo, importVo.getProductNo()));
+
+            if (ObjectUtil.isNotEmpty(existingList)) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,产品编号 ").append(importVo.getProductNo()).append(" 已存在");
+                return;
+            }
+
+            // 构建产品基础信息Bo
+            ProductBaseBo productBo = new ProductBaseBo();
+            productBo.setProductNo(importVo.getProductNo());
+            productBo.setItemName(importVo.getProductName());
+            productBo.setSpecificationsCode(importVo.getSpecificationsCode());
+            productBo.setProductDescription(importVo.getProductDescription());
+            productBo.setPromotionTitle(importVo.getPromotionTitle());
+            productBo.setProductExplain(importVo.getProductExplain());
+            productBo.setBarCoding(importVo.getBarCoding());
+            productBo.setInvoiceName(importVo.getInvoiceName());
+            productBo.setInvoiceSpecs(importVo.getInvoiceSpecs());
+            productBo.setTaxRate(importVo.getTaxRate());
+            productBo.setReferenceLink(importVo.getReferenceLink());
+            productBo.setMarketPrice(importVo.getMarketPrice());
+            productBo.setMemberPrice(importVo.getMemberPrice());
+            productBo.setMinSellingPrice(importVo.getMinSellingPrice());
+            productBo.setPurchasingPrice(importVo.getPurchasingPrice());
+            productBo.setMaxPurchasePrice(importVo.getMaxPurchasePrice());
+
+            // 处理分类信息
+            if (ObjectUtil.isNotEmpty(importVo.getCategoryNo())) {
+                ProductCategory bottomCategory = productCategoryService.getOne(Wrappers.lambdaQuery(ProductCategory.class)
+                    .eq(ProductCategory::getCategoryNo, importVo.getCategoryNo())
+                    .eq(ProductCategory::getPlatform,0L)
+                    .last("limit 1")
+                );
+                if (ObjectUtil.isNotEmpty(bottomCategory)) {
+                    ProductCategory mediumCategory = productCategoryService.getById(bottomCategory.getParentId());
+                    ProductCategory topCategory = productCategoryService.getById(mediumCategory.getParentId());
+                    productBo.setTopCategoryId(topCategory.getId());
+                    productBo.setMediumCategoryId(mediumCategory.getId());
+                    productBo.setBottomCategoryId(bottomCategory.getId());
+                } else {
+                    failureNum++;
+                    failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,产品分类编号 ").append(importVo.getCategoryNo()).append(" 不存在");
+                    return;
+                }
+            }
+
+            // 处理品牌信息
+            if (ObjectUtil.isNotEmpty(importVo.getBrandNo())) {
+                ProductBrand productBrand = productBrandService.getOne(Wrappers.lambdaQuery(ProductBrand.class)
+                    .eq(ProductBrand::getBrandNo, importVo.getBrandNo())
+                    .last("limit 1")
+                );
+                if (ObjectUtil.isNotEmpty(productBrand)) {
+                    productBo.setBrandId(productBrand.getId());
+                } else {
+                    failureNum++;
+                    failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,品牌编号 ").append(importVo.getBrandNo()).append(" 不存在");
+                    return;
+                }
+            }
+
+            // 处理单位信息
+            if (ObjectUtil.isNotEmpty(importVo.getUnitNo())) {
+                ProductUnit productUnit = productUnitService.getOne(Wrappers.lambdaQuery(ProductUnit.class)
+                    .eq(ProductUnit::getUnitNo, importVo.getUnitNo())
+                    .last("limit 1")
+                );
+                if (ObjectUtil.isNotEmpty(productUnit)) {
+                    productBo.setUnitId(String.valueOf(productUnit.getId()));
+                } else {
+                    failureNum++;
+                    failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,单位编号 ").append(importVo.getUnitNo()).append(" 不存在");
+                    return;
+                }
+            }
+
+            // 通过税率编码获取税率ID
+            if (ObjectUtil.isNotEmpty(importVo.getTaxCode())) {
+                try {
+                    RemoteTaxCodeVo taxCodeVo = remoteTaxCodeService.selectByTaxCodeNo(importVo.getTaxCode());
+                    if (taxCodeVo != null) {
+                        productBo.setTaxationId(taxCodeVo.getId());
+                        if (ObjectUtil.isEmpty(importVo.getTaxRate()) && taxCodeVo.getTaxrate() != null) {
+                            productBo.setTaxRate(taxCodeVo.getTaxrate());
+                        }
+                    } else {
+                        failureNum++;
+                        failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,税收编码 ").append(importVo.getTaxCode()).append(" 不存在");
+                        return;
+                    }
+                } catch (Exception e) {
+                    failureNum++;
+                    failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,税收编码查询失败:").append(e.getMessage());
+                    log.error("税收编码查询失败", e);
+                    return;
+                }
+            }
+
+            // 处理上下架状态
+            if (ObjectUtil.isNotEmpty(importVo.getShelfStatus())) {
+                Integer status = "1".equals(importVo.getShelfStatus()) ? 1 : 0;
+                productBo.setProductStatus(status);
+            }
+
+            // 处理起订量
+            if (ObjectUtil.isNotEmpty(importVo.getMinOrderQuantity())) {
+                productBo.setMinOrderQuantity(importVo.getMinOrderQuantity());
+            }
+
+            // 处理主图
+            if (ObjectUtil.isNotEmpty(importVo.getMainImage())) {
+                productBo.setProductImage(importVo.getMainImage());
+            }
+
+            //处理自定义属性
+            if (ObjectUtil.isNotEmpty(importVo.getCustomAttributeName()) && ObjectUtil.isNotEmpty(importVo.getCustomAttributeValue())) {
+                List<ProductClassificationDiy> diyAttributesList = new ArrayList<>();
+                ProductClassificationDiy diyAttributes = new ProductClassificationDiy();
+                diyAttributes.setAttributeKey(importVo.getCustomAttributeName());
+                diyAttributes.setAttributeValue(importVo.getCustomAttributeValue());
+                diyAttributesList.add(diyAttributes);
+                productBo.setDiyAttributesList(diyAttributesList);
+            }
+
+
+            productBo.setCreateBy(LoginHelper.getUserId());
+            productBaseService.insertByBo(productBo);
+
+            successNum++;
+            successMsg.append("<br/>").append(successNum).append("、产品编号 ").append(importVo.getProductNo()).append(" 导入成功");
+
+        } catch (Exception e) {
+            failureNum++;
+            String msg = "<br/>" + failureNum + "、产品编号 " + importVo.getProductNo() + " 导入失败:";
+            String message = e.getMessage();
+            if (e instanceof ConstraintViolationException cvException) {
+                message = StreamUtils.join(cvException.getConstraintViolations(), ConstraintViolation::getMessage, ", ");
+            }
+            failureMsg.append(msg).append(message);
+            log.error(msg, e);
+        }
+    }
+
+    @Override
+    public void doAfterAllAnalysed(AnalysisContext context) {
+        // 所有数据解析完成后调用
+    }
+
+    @Override
+    public ExcelResult<ProductFullImportVo> getExcelResult() {
+        return new ExcelResult<ProductFullImportVo>() {
+
+            @Override
+            public String getAnalysis() {
+                if (failureNum > 0) {
+                    failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
+                    throw new ServiceException(failureMsg.toString());
+                } else {
+                    successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
+                }
+                return successMsg.toString();
+            }
+
+            @Override
+            public List<ProductFullImportVo> getList() {
+                return null;
+            }
+
+            @Override
+            public List<String> getErrorList() {
+                return null;
+            }
+        };
+    }
+}

+ 137 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/listener/ProductImageImportListener.java

@@ -0,0 +1,137 @@
+package org.dromara.product.listener;
+
+import cn.hutool.core.util.ObjectUtil;
+import cn.idev.excel.context.AnalysisContext;
+import cn.idev.excel.event.AnalysisEventListener;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.extern.slf4j.Slf4j;
+import org.dromara.common.core.exception.ServiceException;
+import org.dromara.common.core.utils.SpringUtils;
+import org.dromara.common.excel.core.ExcelListener;
+import org.dromara.common.excel.core.ExcelResult;
+import org.dromara.product.domain.ProductBase;
+import org.dromara.product.domain.bo.ProductBaseBo;
+import org.dromara.product.domain.vo.ProductImageImportVo;
+import org.dromara.product.service.IProductBaseService;
+
+import java.util.List;
+
+/**
+ * 商品图片详情导入监听器
+ *
+ * @author LionLi
+ * @date 2026-06-05
+ */
+@Slf4j
+public class ProductImageImportListener extends AnalysisEventListener<ProductImageImportVo> implements ExcelListener<ProductImageImportVo> {
+
+    private final IProductBaseService productBaseService;
+
+    private int successNum = 0;
+    private int failureNum = 0;
+    private final StringBuilder successMsg = new StringBuilder();
+    private final StringBuilder failureMsg = new StringBuilder();
+
+    public ProductImageImportListener(IProductBaseService productBaseService) {
+        this.productBaseService = productBaseService;
+    }
+
+    @Override
+    public void invoke(ProductImageImportVo importVo, AnalysisContext context) {
+        try {
+            int rowNum = context.readRowHolder().getRowIndex() + 1;
+
+            // 校验必填字段
+            if (ObjectUtil.isEmpty(importVo.getProductNo())) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,商品编号不能为空");
+                return;
+            }
+
+            // 检查商品编号是否存在
+            ProductBase productBase = productBaseService.getOne(Wrappers.<ProductBase>lambdaQuery()
+                .eq(ProductBase::getProductNo, importVo.getProductNo()));
+
+            if (ObjectUtil.isEmpty(productBase)) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,商品编号 ").append(importVo.getProductNo()).append(" 不存在");
+                return;
+            }
+
+            ProductBaseBo productBo = new ProductBaseBo();
+            productBo.setId(productBase.getId());
+
+            boolean hasUpdate = false;
+
+            // 处理主图
+            if (ObjectUtil.isNotEmpty(importVo.getMainImage())) {
+                productBo.setProductImage(importVo.getMainImage());
+                hasUpdate = true;
+            }
+
+            // 处理轮播图(多图)
+            if (ObjectUtil.isNotEmpty(importVo.getCarouselImages())) {
+                productBo.setImageUrl(importVo.getCarouselImages());
+                hasUpdate = true;
+            }
+
+            // 处理商品详情(PC端和移动端)
+            if (ObjectUtil.isNotEmpty(importVo.getProductDetail())) {
+                productBo.setPcDetail(importVo.getProductDetail());
+                productBo.setMobileDetail(importVo.getProductDetail());
+                hasUpdate = true;
+            }
+
+            if (!hasUpdate) {
+                failureNum++;
+                failureMsg.append("<br/>").append(failureNum).append("、第 ").append(rowNum).append(" 行,至少需要填写主图、轮播图或商品详情中的一个");
+                return;
+            }
+
+            // 调用Service进行更新
+            productBaseService.updateByBo(productBo);
+
+            successNum++;
+            successMsg.append("<br/>").append(successNum).append("、商品编号 ").append(importVo.getProductNo()).append(" 导入成功");
+
+        } catch (Exception e) {
+            failureNum++;
+            String msg = "<br/>" + failureNum + "、商品编号 " + importVo.getProductNo() + " 导入失败:";
+            String message = e.getMessage();
+            failureMsg.append(msg).append(message);
+            log.error(msg, e);
+        }
+    }
+
+    @Override
+    public void doAfterAllAnalysed(AnalysisContext context) {
+        // 所有数据解析完成后调用
+    }
+
+    @Override
+    public ExcelResult<ProductImageImportVo> getExcelResult() {
+        return new ExcelResult<ProductImageImportVo>() {
+
+            @Override
+            public String getAnalysis() {
+                if (failureNum > 0) {
+                    failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
+                    throw new ServiceException(failureMsg.toString());
+                } else {
+                    successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
+                }
+                return successMsg.toString();
+            }
+
+            @Override
+            public List<ProductImageImportVo> getList() {
+                return null;
+            }
+
+            @Override
+            public List<String> getErrorList() {
+                return null;
+            }
+        };
+    }
+}

+ 89 - 76
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBaseServiceImpl.java

@@ -249,6 +249,7 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
         extendWrapper.eq(ProductExtend::getProductId, id);
         ProductExtend extend = extendMapper.selectOne(extendWrapper);
         if (extend != null) {
+            vo.setProductExplain(extend.getProductExplain());
             vo.setPromotionTitle(extend.getPromotionTitle());
             vo.setSpecificationsCode(extend.getSpecificationsCode());
             vo.setInvoiceName(extend.getInvoiceName());
@@ -484,15 +485,15 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
         if (ObjectUtil.isNotEmpty(bo.getBrandId())) {
             wrapper.eq(ProductBaseVo::getBrandId, bo.getBrandId());
         }
-        if (ObjectUtil.isNotEmpty(bo.getTopCategoryId())) {
-            wrapper.eq(ProductBaseVo::getTopCategoryId, bo.getTopCategoryId());
-        }
-        if (ObjectUtil.isNotEmpty(bo.getMediumCategoryId())) {
-            wrapper.eq(ProductBaseVo::getMediumCategoryId, bo.getMediumCategoryId());
-        }
-        if (ObjectUtil.isNotEmpty(bo.getBottomCategoryId())) {
-            wrapper.eq(ProductBaseVo::getBottomCategoryId, bo.getBottomCategoryId());
-        }
+//        if (ObjectUtil.isNotEmpty(bo.getTopCategoryId())) {
+//            wrapper.eq(ProductBaseVo::getTopCategoryId, bo.getTopCategoryId());
+//        }
+//        if (ObjectUtil.isNotEmpty(bo.getMediumCategoryId())) {
+//            wrapper.eq(ProductBaseVo::getMediumCategoryId, bo.getMediumCategoryId());
+//        }
+//        if (ObjectUtil.isNotEmpty(bo.getBottomCategoryId())) {
+//            wrapper.eq(ProductBaseVo::getBottomCategoryId, bo.getBottomCategoryId());
+//        }
         if (ObjectUtil.isNotEmpty(bo.getIsCustomize())) {
             wrapper.eq(ProductBaseVo::getIsCustomize, bo.getIsCustomize());
         }
@@ -1514,39 +1515,39 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
     @Override
     public TableDataInfo<PcProductVo> getPcProductPage(PcProductBo bo, PageQuery pageQuery) {
         //获取当前登录用户的商品池
-        List<Long> productIds = new ArrayList<>();
-        Map<Long, BigDecimal> agreementPriceMap = new HashMap<>();
-
-        //是否是大客户
-        if(ObjectUtil.isNotEmpty(LoginHelper.getLoginUser().getCustomerId())) {
-            ClientSiteVo clientSiteVo = clientSiteMapper.selectVoOne(Wrappers.lambdaQuery(ClientSite.class)
-                .eq(ClientSite::getClientId, LoginHelper.getLoginUser().getCustomerId())
-                .orderByDesc(ClientSite::getCreateTime)
-                .last("limit 1")
-            );
-            if (ObjectUtil.isNotEmpty(clientSiteVo)) {
-                Long isDiy = clientSiteVo.getIsDiy();
-                if (ObjectUtil.isNotEmpty(isDiy) && isDiy == 1) {
-                    //获取协议商品池商品
-                    List<ProtocolProducts> protocolProducts = protocolProductsMapper.selectList(Wrappers.lambdaQuery(ProtocolProducts.class)
-                        .eq(ProtocolProducts::getCustomerId, LoginHelper.getLoginUser().getCustomerId())
-                        .eq(ProtocolProducts::getAuditStatus, 2)
-                    );
-                    if (CollUtil.isNotEmpty(protocolProducts)) {
-                        agreementPriceMap = protocolProducts.stream().collect(Collectors.toMap(ProtocolProducts::getProductId, ProtocolProducts::getAgreementPrice));
-                        productIds = protocolProducts.stream().map(ProtocolProducts::getProductId).toList();
-                    }
-                }
-            }
-        }else {
-            //获取企业的商品池商品
-            if(Objects.equals(LoginHelper.getLoginUser().getUserSonType(),"3")) {
-                List<ClientSiteProduct> clientSiteProducts = clientSiteProductMapper.selectList();
-                if (CollUtil.isNotEmpty(clientSiteProducts)) {
-                    productIds = clientSiteProducts.stream().map(ClientSiteProduct::getProductId).toList();
-                }
-            }
-        }
+//        List<Long> productIds = new ArrayList<>();
+//        Map<Long, BigDecimal> agreementPriceMap = new HashMap<>();
+//
+//        //是否是大客户
+//        if(ObjectUtil.isNotEmpty(LoginHelper.getLoginUser().getCustomerId())) {
+//            ClientSiteVo clientSiteVo = clientSiteMapper.selectVoOne(Wrappers.lambdaQuery(ClientSite.class)
+//                .eq(ClientSite::getClientId, LoginHelper.getLoginUser().getCustomerId())
+//                .orderByDesc(ClientSite::getCreateTime)
+//                .last("limit 1")
+//            );
+//            if (ObjectUtil.isNotEmpty(clientSiteVo)) {
+//                Long isDiy = clientSiteVo.getIsDiy();
+//                if (ObjectUtil.isNotEmpty(isDiy) && isDiy == 1) {
+//                    //获取协议商品池商品
+//                    List<ProtocolProducts> protocolProducts = protocolProductsMapper.selectList(Wrappers.lambdaQuery(ProtocolProducts.class)
+//                        .eq(ProtocolProducts::getCustomerId, LoginHelper.getLoginUser().getCustomerId())
+//                        .eq(ProtocolProducts::getAuditStatus, 2)
+//                    );
+//                    if (CollUtil.isNotEmpty(protocolProducts)) {
+//                        agreementPriceMap = protocolProducts.stream().collect(Collectors.toMap(ProtocolProducts::getProductId, ProtocolProducts::getAgreementPrice));
+//                        productIds = protocolProducts.stream().map(ProtocolProducts::getProductId).toList();
+//                    }
+//                }
+//            }
+//        }else {
+//            //获取企业的商品池商品
+//            if(Objects.equals(LoginHelper.getLoginUser().getUserSonType(),"3")) {
+//                List<ClientSiteProduct> clientSiteProducts = clientSiteProductMapper.selectList();
+//                if (CollUtil.isNotEmpty(clientSiteProducts)) {
+//                    productIds = clientSiteProducts.stream().map(ClientSiteProduct::getProductId).toList();
+//                }
+//            }
+//        }
 
 //        //是否是企业购
 //        QueryWrapper<ProductBase> lqw = Wrappers.query(ProductBase.class);
@@ -1651,43 +1652,55 @@ public class ProductBaseServiceImpl extends ServiceImpl<ProductBaseMapper, Produ
         // 使用ES分页查询
         try {
             LambdaEsQueryWrapper<ProductBaseVo> esQueryWrapper = buildEsQueryWrapperForPc(bo);
-            if(Objects.equals(LoginHelper.getLoginUser().getUserSonType(),"3")){
-                if (ObjectUtil.isNotEmpty(productIds) ){
-                    esQueryWrapper.in(ProductBaseVo::getId, productIds);
+            if(LoginHelper.isLogin()){
+                if(Objects.equals(LoginHelper.getLoginUser().getUserSonType(),"3")){
+//                if (ObjectUtil.isNotEmpty(productIds) ){
+//                    esQueryWrapper.in(ProductBaseVo::getId, productIds);
+//                }else {
+//                    esQueryWrapper.eq(ProductBaseVo::getId, 0L);
+//                }
+                    if (ObjectUtil.isNotEmpty(bo.getTopCategoryId())){
+                        //获取分类绑定的商品
+                        List<ProductCategoryBinding> productCategoryBindings = productCategoryBindingMapper.selectList(Wrappers.lambdaQuery(ProductCategoryBinding.class)
+                            .eq(ProductCategoryBinding::getTopCategoryId, bo.getTopCategoryId())
+                        );
+                        if (CollUtil.isNotEmpty(productCategoryBindings)) {
+                            esQueryWrapper.in(ProductBaseVo::getId, productCategoryBindings.stream().map(ProductCategoryBinding::getProductId).toList());
+                        }else {
+                            esQueryWrapper.eq(ProductBaseVo::getId, 0L);
+                        }
+                    }
+                    if (ObjectUtil.isNotEmpty(bo.getMediumCategoryId())){
+                        //获取分类绑定的商品
+                        List<ProductCategoryBinding> productCategoryBindings = productCategoryBindingMapper.selectList(Wrappers.lambdaQuery(ProductCategoryBinding.class)
+                            .eq(ProductCategoryBinding::getMediumCategoryId, bo.getMediumCategoryId())
+                        );
+                        if (CollUtil.isNotEmpty(productCategoryBindings)) {
+                            esQueryWrapper.in(ProductBaseVo::getId, productCategoryBindings.stream().map(ProductCategoryBinding::getProductId).toList());
+                        }else {
+                            esQueryWrapper.eq(ProductBaseVo::getId, 0L);
+                        }
+                    }
+                    if (ObjectUtil.isNotEmpty(bo.getBottomCategoryId())){
+                        //获取分类绑定的商品
+                        List<ProductCategoryBinding> productCategoryBindings = productCategoryBindingMapper.selectList(Wrappers.lambdaQuery(ProductCategoryBinding.class)
+                            .eq(ProductCategoryBinding::getBottomCategoryId, bo.getBottomCategoryId())
+                        );
+                        if (CollUtil.isNotEmpty(productCategoryBindings)) {
+                            esQueryWrapper.in(ProductBaseVo::getId, productCategoryBindings.stream().map(ProductCategoryBinding::getProductId).toList());
+                        }else {
+                            esQueryWrapper.eq(ProductBaseVo::getId, 0L);
+                        }
+                    }
                 }else {
-                    esQueryWrapper.eq(ProductBaseVo::getId, 0L);
-                }
-                if (ObjectUtil.isNotEmpty(bo.getTopCategoryId())){
-                    //获取分类绑定的商品
-                    List<ProductCategoryBinding> productCategoryBindings = productCategoryBindingMapper.selectList(Wrappers.lambdaQuery(ProductCategoryBinding.class)
-                        .eq(ProductCategoryBinding::getTopCategoryId, bo.getTopCategoryId())
-                    );
-                    if (CollUtil.isNotEmpty(productCategoryBindings)) {
-                        esQueryWrapper.in(ProductBaseVo::getId, productCategoryBindings.stream().map(ProductCategoryBinding::getProductId).toList());
-                    }else {
-                        esQueryWrapper.eq(ProductBaseVo::getId, 0L);
+                    if (ObjectUtil.isNotEmpty(bo.getTopCategoryId())) {
+                        esQueryWrapper.eq(ProductBaseVo::getTopCategoryId, bo.getTopCategoryId());
                     }
-                }
-                if (ObjectUtil.isNotEmpty(bo.getMediumCategoryId())){
-                    //获取分类绑定的商品
-                    List<ProductCategoryBinding> productCategoryBindings = productCategoryBindingMapper.selectList(Wrappers.lambdaQuery(ProductCategoryBinding.class)
-                        .eq(ProductCategoryBinding::getMediumCategoryId, bo.getMediumCategoryId())
-                    );
-                    if (CollUtil.isNotEmpty(productCategoryBindings)) {
-                        esQueryWrapper.in(ProductBaseVo::getId, productCategoryBindings.stream().map(ProductCategoryBinding::getProductId).toList());
-                    }else {
-                        esQueryWrapper.eq(ProductBaseVo::getId, 0L);
+                    if (ObjectUtil.isNotEmpty(bo.getMediumCategoryId())) {
+                        esQueryWrapper.eq(ProductBaseVo::getMediumCategoryId, bo.getMediumCategoryId());
                     }
-                }
-                if (ObjectUtil.isNotEmpty(bo.getBottomCategoryId())){
-                    //获取分类绑定的商品
-                    List<ProductCategoryBinding> productCategoryBindings = productCategoryBindingMapper.selectList(Wrappers.lambdaQuery(ProductCategoryBinding.class)
-                        .eq(ProductCategoryBinding::getBottomCategoryId, bo.getBottomCategoryId())
-                    );
-                    if (CollUtil.isNotEmpty(productCategoryBindings)) {
-                        esQueryWrapper.in(ProductBaseVo::getId, productCategoryBindings.stream().map(ProductCategoryBinding::getProductId).toList());
-                    }else {
-                        esQueryWrapper.eq(ProductBaseVo::getId, 0L);
+                    if (ObjectUtil.isNotEmpty(bo.getBottomCategoryId())) {
+                        esQueryWrapper.eq(ProductBaseVo::getBottomCategoryId, bo.getBottomCategoryId());
                     }
                 }
             }

+ 20 - 21
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBrandServiceImpl.java

@@ -312,30 +312,29 @@ public class ProductBrandServiceImpl  extends ServiceImpl<ProductBrandMapper, Pr
      */
     @Override
     public TableDataInfo<ProductBrandVo> getBrandByCategoryList(Long categoryId,String name,String initial,PageQuery pageQuery) {
-        List<Long> brandIds = null;
-        if(ObjectUtil.isNotEmpty(categoryId) || ObjectUtil.isNotEmpty(name)){
-            List<ProductBaseVo> productBaseVos = productEsMapper.selectList(new LambdaEsQueryWrapper<ProductBaseVo>()
-                .select(ProductBaseVo::getBrandId)
-                .eq(ObjectUtil.isNotEmpty(categoryId),ProductBaseVo::getTopCategoryId, categoryId)
-                .like(ObjectUtil.isNotEmpty(name),ProductBaseVo::getBrandName, name)
-                .groupBy(ProductBaseVo::getBrandId)
-            );
-            if (CollUtil.isEmpty(productBaseVos)) {
-                return TableDataInfo.build();
-            }
-            brandIds = productBaseVos.stream().map(ProductBaseVo::getBrandId).toList();
-        }
+//        List<Long> brandIds = null;
+//        if(ObjectUtil.isNotEmpty(categoryId) || ObjectUtil.isNotEmpty(name)){
+//            List<ProductBaseVo> productBaseVos = productEsMapper.selectList(new LambdaEsQueryWrapper<ProductBaseVo>()
+//                .select(ProductBaseVo::getBrandId)
+//                .eq(ObjectUtil.isNotEmpty(categoryId),ProductBaseVo::getTopCategoryId, categoryId)
+//                .like(ObjectUtil.isNotEmpty(name),ProductBaseVo::getBrandName, name)
+//            );
+//            if (CollUtil.isEmpty(productBaseVos)) {
+//                return TableDataInfo.build();
+//            }
+//            brandIds = productBaseVos.stream().map(ProductBaseVo::getBrandId).toList();
+//        }
 
         LambdaQueryWrapper<ProductBrand> lqw = Wrappers.lambdaQuery(ProductBrand.class);
-        if(ObjectUtil.isNotEmpty(categoryId) || ObjectUtil.isNotEmpty(name)){
-            if(ObjectUtil.isNotEmpty(brandIds)){
-                lqw.in(ProductBrand::getId,brandIds);
-            }else {
-                lqw.isNull(ProductBrand::getId);
-            }
-        }
+//        if(ObjectUtil.isNotEmpty(categoryId) || ObjectUtil.isNotEmpty(name)){
+//            if(ObjectUtil.isNotEmpty(brandIds)){
+//                lqw.in(ProductBrand::getId,brandIds);
+//            }else {
+//                lqw.isNull(ProductBrand::getId);
+//            }
+//        }
         lqw.eq(ObjectUtil.isNotEmpty(initial),ProductBrand::getBrandInitials,initial);
-        lqw.eq(ProductBrand::getIsShow,1);
+//        lqw.eq(ProductBrand::getIsShow,1);
         Page<ProductBrandVo> productBrandVos = baseMapper.selectVoPage(pageQuery.build(),lqw);
         return TableDataInfo.build(productBrandVos);
     }

+ 64 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteTaxCodeServiceImpl.java

@@ -0,0 +1,64 @@
+package org.dromara.system.dubbo;
+
+import cn.hutool.core.bean.BeanUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.dromara.system.api.RemoteTaxCodeService;
+import org.dromara.system.api.domain.vo.RemoteTaxCodeVo;
+import org.dromara.system.domain.SysTaxCode;
+import org.dromara.system.service.ISysTaxCodeService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * @author
+ * @date 2026/6/5 下午3:06
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+@DubboService
+public class RemoteTaxCodeServiceImpl implements RemoteTaxCodeService {
+    private final ISysTaxCodeService sysTaxCodeService;
+
+    /**
+     * 通过税率编码编号获取税率编码对象
+     *
+     * @param taxCodeNo
+     */
+    @Override
+    public RemoteTaxCodeVo selectByTaxCodeNo(String taxCodeNo) {
+        SysTaxCode sysTaxCode = sysTaxCodeService.getOne(Wrappers.lambdaQuery(SysTaxCode.class)
+            .eq(SysTaxCode::getTaxationNo, taxCodeNo)
+        );
+
+        return BeanUtil.toBean(sysTaxCode, RemoteTaxCodeVo.class);
+    }
+
+    /**
+     * 通过税率Id获取税率编码对象
+     *
+     * @param taxCodeId
+     */
+    @Override
+    public RemoteTaxCodeVo selectByTaxCodeId(Long taxCodeId) {
+        SysTaxCode sysTaxCode = sysTaxCodeService.getOne(Wrappers.lambdaQuery(SysTaxCode.class)
+            .eq(SysTaxCode::getId, taxCodeId)
+        );
+        return BeanUtil.toBean(sysTaxCode, RemoteTaxCodeVo.class);
+    }
+
+    /**
+     * 获取所有的税率编码对象
+     *
+     * @param remoteTaxCodeVo
+     */
+    @Override
+    public List<RemoteTaxCodeVo> selectAll(RemoteTaxCodeVo remoteTaxCodeVo) {
+        List<SysTaxCode> list = sysTaxCodeService.list();
+        return BeanUtil.copyToList(list, RemoteTaxCodeVo.class);
+    }
+}