林小张 3 сар өмнө
parent
commit
d35076d730
34 өөрчлөгдсөн 1476 нэмэгдсэн , 18 устгасан
  1. 18 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteCreditLevelService.java
  2. 10 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerInfoController.java
  3. 34 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerListBo.java
  4. 55 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerListVo.java
  5. 8 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerInfoMapper.java
  6. 11 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerInfoService.java
  7. 49 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerInfoServiceImpl.java
  8. 46 0
      ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/CustomerInfoMapper.xml
  9. 12 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductAssociateController.java
  10. 8 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductBaseController.java
  11. 118 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductOperationInfoController.java
  12. 22 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductProgramLinkController.java
  13. 8 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductSpecsController.java
  14. 82 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductOperationInfo.java
  15. 82 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductOperationInfoBo.java
  16. 94 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductOperationInfoVo.java
  17. 85 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductOperationVo.java
  18. 10 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductBaseMapper.java
  19. 15 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductOperationInfoMapper.java
  20. 8 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductAssociateService.java
  21. 10 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductBaseService.java
  22. 78 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductOperationInfoService.java
  23. 17 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductProgramLinkService.java
  24. 8 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductSpecsService.java
  25. 136 9
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductAssociateServiceImpl.java
  26. 14 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBaseServiceImpl.java
  27. 154 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductOperationInfoServiceImpl.java
  28. 34 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductProgramLinkServiceImpl.java
  29. 127 9
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductSpecsServiceImpl.java
  30. 58 0
      ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductBaseMapper.xml
  31. 7 0
      ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductOperationInfoMapper.xml
  32. 26 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteCreditLevelServiceImpl.java
  33. 10 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ICreditLevelService.java
  34. 22 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/CreditLevelServiceImpl.java

+ 18 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteCreditLevelService.java

@@ -0,0 +1,18 @@
+package org.dromara.system.api;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 信用等级远程服务接口
+ */
+public interface RemoteCreditLevelService {
+
+    /**
+     * 根据ID批量查询信用等级名称
+     *
+     * @param ids 信用等级ID集合
+     * @return Map<ID, 名称>
+     */
+    Map<Long, String> selectCreditLevelNameByIds(Set<Long> ids);
+}

+ 10 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerInfoController.java

@@ -8,9 +8,11 @@ import jakarta.validation.constraints.*;
 import cn.dev33.satoken.annotation.SaCheckPermission;
 import org.dromara.customer.domain.CustomerSalesInfo;
 import org.dromara.customer.domain.bo.CustomerSalesInfoBo;
+import org.dromara.customer.domain.bo.CustomerListBo;
 import org.dromara.customer.domain.bo.MessagePublishCustomerBo;
 import org.dromara.customer.domain.dto.SetCustomerInfoTagDto;
 import org.dromara.customer.domain.vo.ContractVo;
+import org.dromara.customer.domain.vo.CustomerListVo;
 import org.dromara.customer.domain.vo.MessagePublishCustomerVo;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.validation.annotation.Validated;
@@ -67,6 +69,14 @@ public class CustomerInfoController extends BaseController {
         return customerInfoService.queryContractPageList(bo, pageQuery);
     }
 
+    /**
+     * 客户列表-分页查询
+     */
+    @GetMapping("/customerList")
+    public TableDataInfo<CustomerListVo> customerList(CustomerListBo bo, PageQuery pageQuery) {
+        return customerInfoService.queryCustomerListPage(bo, pageQuery);
+    }
+
     /**
      * 导出客户信息列表
      */

+ 34 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/CustomerListBo.java

@@ -0,0 +1,34 @@
+package org.dromara.customer.domain.bo;
+
+import lombok.Data;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+
+import java.math.BigDecimal;
+
+/**
+ * 客户列表查询条件
+ */
+@Data
+public class CustomerListBo extends BaseEntity {
+
+    /** 客户ID */
+    private Long id;
+
+    /** 客户编号 */
+    private String customerNo;
+
+    /** 行业类别ID */
+    private Long industryCategoryId;
+
+    /** 年度销售最小值 */
+    private BigDecimal yearSalesMin;
+
+    /** 年度销售最大值 */
+    private BigDecimal yearSalesMax;
+
+    /** 业务员ID */
+    private Long salesPersonId;
+
+    /** 客户等级ID */
+    private Long customerLevelId;
+}

+ 55 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerListVo.java

@@ -0,0 +1,55 @@
+package org.dromara.customer.domain.vo;
+
+import lombok.Data;
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 客户列表视图对象
+ */
+@Data
+public class CustomerListVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 客户ID */
+    private Long id;
+
+    /** 客户编号 */
+    private String customerNo;
+
+    /** 客户名称 */
+    private String customerName;
+
+    /** 行业类别ID */
+    private Long industryCategoryId;
+
+    /** 行业名称 */
+    private String industryName;
+
+    /** 年销售预估(万) */
+    private BigDecimal yearSalesEstimate;
+
+    /** 信用额度 */
+    private BigDecimal creditAmount;
+
+    /** 信用等级ID */
+    private Long customerLevelId;
+
+    /** 信用等级名称 */
+    private String customerLevelName;
+
+    /** 应收账款 */
+    private BigDecimal accountsReceivable;
+
+    /** 业务员ID */
+    private Long salesPersonId;
+
+    /** 业务员名称 */
+    private String salesPersonName;
+
+    /** 状态 */
+    private String status;
+}

+ 8 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerInfoMapper.java

@@ -3,9 +3,11 @@ package org.dromara.customer.mapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import org.apache.ibatis.annotations.Param;
 import org.dromara.customer.domain.CustomerInfo;
+import org.dromara.customer.domain.bo.CustomerListBo;
 import org.dromara.customer.domain.bo.MessagePublishCustomerBo;
 import org.dromara.customer.domain.vo.CustomerInfoVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.vo.CustomerListVo;
 import org.dromara.customer.domain.vo.MessagePublishCustomerVo;
 
 /**
@@ -21,4 +23,10 @@ public interface CustomerInfoMapper extends BaseMapperPlus<CustomerInfo, Custome
      */
     Page<MessagePublishCustomerVo> selectMessagePublishCustomerPage(@Param("page") Page<MessagePublishCustomerVo> page,
                                                                      @Param("bo") MessagePublishCustomerBo bo);
+
+    /**
+     * 客户列表-分页查询
+     */
+    Page<CustomerListVo> selectCustomerListPage(@Param("page") Page<CustomerListVo> page,
+                                                 @Param("bo") CustomerListBo bo);
 }

+ 11 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerInfoService.java

@@ -2,12 +2,14 @@ package org.dromara.customer.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
 import org.dromara.customer.domain.CustomerInfo;
+import org.dromara.customer.domain.bo.CustomerListBo;
 import org.dromara.customer.domain.bo.MessagePublishCustomerBo;
 import org.dromara.customer.domain.vo.ContractVo;
 import org.dromara.customer.domain.vo.CustomerInfoVo;
 import org.dromara.customer.domain.bo.CustomerInfoBo;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.customer.domain.vo.CustomerListVo;
 import org.dromara.customer.domain.vo.MessagePublishCustomerVo;
 
 import java.math.BigDecimal;
@@ -57,6 +59,15 @@ public interface ICustomerInfoService extends IService<CustomerInfo> {
      */
     TableDataInfo<MessagePublishCustomerVo> queryMessagePublishCustomerPage(MessagePublishCustomerBo bo, PageQuery pageQuery);
 
+    /**
+     * 客户列表-分页查询
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 客户列表
+     */
+    TableDataInfo<CustomerListVo> queryCustomerListPage(CustomerListBo bo, PageQuery pageQuery);
+
     /**
      * 查询符合条件的客户信息列表
      *

+ 49 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerInfoServiceImpl.java

@@ -52,6 +52,9 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
     @DubboReference
     private RemoteComCompanyService remoteComCompanyService;
 
+    @DubboReference
+    private RemoteCreditLevelService remoteCreditLevelService;
+
     private final CustomerInfoMapper baseMapper;
     private final CustomerBusinessInfoMapper customerBusinessInfoMapper;
     private final CustomerInvoiceInfoMapper customerInvoiceInfoMapper;
@@ -401,6 +404,52 @@ public class CustomerInfoServiceImpl extends ServiceImpl<CustomerInfoMapper, Cus
         return TableDataInfo.build(page);
     }
 
+    /**
+     * 客户列表-分页查询
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 客户列表
+     */
+    @Override
+    public TableDataInfo<CustomerListVo> queryCustomerListPage(CustomerListBo bo, PageQuery pageQuery) {
+        // 设置平台标识
+        if (StringUtils.isBlank(bo.getPlatformCode())) {
+            bo.setPlatformCode(PlatformContext.getPlatform());
+        }
+        Page<CustomerListVo> page = baseMapper.selectCustomerListPage(pageQuery.build(), bo);
+
+        // 补充业务员名称和信用等级名称
+        List<CustomerListVo> records = page.getRecords();
+        if (CollUtil.isNotEmpty(records)) {
+            // 收集业务员ID
+            Set<Long> staffIds = records.stream()
+                .map(CustomerListVo::getSalesPersonId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+            // 收集信用等级ID
+            Set<Long> creditLevelIds = records.stream()
+                .map(CustomerListVo::getCustomerLevelId)
+                .filter(Objects::nonNull)
+                .collect(Collectors.toSet());
+
+            // 批量查询业务员名称
+            if (!staffIds.isEmpty()) {
+                Map<Long, String> staffMap = remoteComStaffService.selectStaffNameByIds(staffIds);
+                records.forEach(vo -> vo.setSalesPersonName(staffMap.get(vo.getSalesPersonId())));
+            }
+
+            // 批量查询信用等级名称
+            if (!creditLevelIds.isEmpty()) {
+                Map<Long, String> creditLevelMap = remoteCreditLevelService.selectCreditLevelNameByIds(creditLevelIds);
+                records.forEach(vo -> vo.setCustomerLevelName(creditLevelMap.get(vo.getCustomerLevelId())));
+            }
+        }
+
+        return TableDataInfo.build(page);
+    }
+
     /**
      * 查询符合条件的客户信息列表
      *

+ 46 - 0
ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/CustomerInfoMapper.xml

@@ -50,4 +50,50 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ORDER BY ci.id DESC
     </select>
 
+    <!-- 客户列表-分页查询 -->
+    <select id="selectCustomerListPage" resultType="org.dromara.customer.domain.vo.CustomerListVo">
+        SELECT
+            ci.id,
+            ci.customer_no AS customerNo,
+            IFNULL(ci.customer_name, ci.business_customer_name) AS customerName,
+            ci.industry_category_id AS industryCategoryId,
+            ic.industry_category_name AS industryName,
+            csi.credit_amount AS creditAmount,
+            ci.customer_level_id AS customerLevelId,
+            csi.accounts_receivable AS accountsReceivable,
+            csi.sales_person_id AS salesPersonId,
+            ci.status
+        FROM customer_info ci
+        LEFT JOIN customer_sales_info csi ON ci.id = csi.customer_id AND csi.del_flag = '0'
+        LEFT JOIN industry_category ic ON ci.industry_category_id = ic.id AND ic.del_flag = '0'
+        <where>
+            ci.del_flag = '0'
+            <if test="bo.platformCode != null and bo.platformCode != ''">
+                AND ci.platform_code = #{bo.platformCode}
+            </if>
+            <if test="bo.id != null">
+                AND ci.id = #{bo.id}
+            </if>
+            <if test="bo.customerNo != null and bo.customerNo != ''">
+                AND ci.customer_no LIKE CONCAT('%', #{bo.customerNo}, '%')
+            </if>
+            <if test="bo.industryCategoryId != null">
+                AND ci.industry_category_id = #{bo.industryCategoryId}
+            </if>
+            <if test="bo.customerLevelId != null">
+                AND ci.customer_level_id = #{bo.customerLevelId}
+            </if>
+            <if test="bo.salesPersonId != null">
+                AND csi.sales_person_id = #{bo.salesPersonId}
+            </if>
+            <if test="bo.yearSalesMin != null">
+                AND csi.credit_amount &gt;= #{bo.yearSalesMin}
+            </if>
+            <if test="bo.yearSalesMax != null">
+                AND csi.credit_amount &lt;= #{bo.yearSalesMax}
+            </if>
+        </where>
+        ORDER BY ci.id DESC
+    </select>
+
 </mapper>

+ 12 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductAssociateController.java

@@ -69,6 +69,18 @@ public class ProductAssociateController extends BaseController {
         return R.ok(productAssociateService.queryById(id));
     }
 
+    /**
+     * 根据产品ID获取关联信息
+     *
+     * @param productId 产品ID
+     */
+    //@SaCheckPermission("product:associate:query")
+    @GetMapping("/product/{productId}")
+    public R<ProductAssociateVo> getByProductId(@NotNull(message = "产品ID不能为空")
+                                     @PathVariable("productId") Long productId) {
+        return R.ok(productAssociateService.queryByProductId(productId));
+    }
+
     /**
      * 新增产品关联
      */

+ 8 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductBaseController.java

@@ -192,4 +192,12 @@ public class ProductBaseController extends BaseController {
         return productBaseService.queryRecommendProductPageList(bo, pageQuery);
     }
 
+    /**
+     * 查询商品运营列表(联表查询,用于客户运营-商品运营页面)
+     */
+    @GetMapping("/operation/list")
+    public TableDataInfo<ProductOperationVo> operationList(ProductBaseBo bo, PageQuery pageQuery) {
+        return productBaseService.queryProductOperationPageList(bo, pageQuery);
+    }
+
 }

+ 118 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductOperationInfoController.java

@@ -0,0 +1,118 @@
+package org.dromara.product.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.validation.annotation.Validated;
+import org.dromara.common.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.core.domain.R;
+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.product.domain.vo.ProductOperationInfoVo;
+import org.dromara.product.domain.bo.ProductOperationInfoBo;
+import org.dromara.product.service.IProductOperationInfoService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 产品运营信息
+ * 前端访问路由地址为:/product/operationInfo
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/operationInfo")
+public class ProductOperationInfoController extends BaseController {
+
+    private final IProductOperationInfoService productOperationInfoService;
+
+    /**
+     * 查询产品运营信息列表
+     */
+    @SaCheckPermission("product:operationInfo:list")
+    @GetMapping("/list")
+    public TableDataInfo<ProductOperationInfoVo> list(ProductOperationInfoBo bo, PageQuery pageQuery) {
+        return productOperationInfoService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出产品运营信息列表
+     */
+    @SaCheckPermission("product:operationInfo:export")
+    @Log(title = "产品运营信息", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ProductOperationInfoBo bo, HttpServletResponse response) {
+        List<ProductOperationInfoVo> list = productOperationInfoService.queryList(bo);
+        ExcelUtil.exportExcel(list, "产品运营信息", ProductOperationInfoVo.class, response);
+    }
+
+    /**
+     * 获取产品运营信息详细信息
+     *
+     * @param id 主键
+     */
+    @SaCheckPermission("product:operationInfo:query")
+    @GetMapping("/{id}")
+    public R<ProductOperationInfoVo> getInfo(@NotNull(message = "主键不能为空")
+                                     @PathVariable("id") Long id) {
+        return R.ok(productOperationInfoService.queryById(id));
+    }
+
+    /**
+     * 根据产品ID获取运营信息
+     *
+     * @param productId 产品ID
+     */
+    @SaCheckPermission("product:operationInfo:query")
+    @GetMapping("/product/{productId}")
+    public R<ProductOperationInfoVo> getByProductId(@NotNull(message = "产品ID不能为空")
+                                     @PathVariable("productId") Long productId) {
+        return R.ok(productOperationInfoService.queryByProductId(productId));
+    }
+
+    /**
+     * 新增产品运营信息
+     */
+    @SaCheckPermission("product:operationInfo:add")
+    @Log(title = "产品运营信息", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody ProductOperationInfoBo bo) {
+        return toAjax(productOperationInfoService.insertByBo(bo));
+    }
+
+    /**
+     * 修改产品运营信息
+     */
+    @SaCheckPermission("product:operationInfo:edit")
+    @Log(title = "产品运营信息", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ProductOperationInfoBo bo) {
+        return toAjax(productOperationInfoService.updateByBo(bo));
+    }
+
+    /**
+     * 删除产品运营信息
+     *
+     * @param ids 主键串
+     */
+    @SaCheckPermission("product:operationInfo:remove")
+    @Log(title = "产品运营信息", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(productOperationInfoService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 22 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductProgramLinkController.java

@@ -1,6 +1,7 @@
 package org.dromara.product.controller;
 
 import java.util.List;
+import java.util.Map;
 
 import lombok.RequiredArgsConstructor;
 import jakarta.servlet.http.HttpServletResponse;
@@ -103,4 +104,25 @@ public class ProductProgramLinkController extends BaseController {
                           @PathVariable("ids") Long[] ids) {
         return toAjax(productProgramLinkService.deleteWithValidByIds(List.of(ids), true));
     }
+
+    /**
+     * 根据产品ID查询已关联的方案ID列表
+     */
+    @GetMapping("/product/{productId}")
+    public R<List<Long>> getProgramIdsByProductId(@PathVariable("productId") Long productId) {
+        return R.ok(productProgramLinkService.queryProgramIdsByProductId(productId));
+    }
+
+    /**
+     * 批量保存产品方案关联
+     */
+    @Log(title = "项目方案关联", businessType = BusinessType.UPDATE)
+    @PostMapping("/saveBatch")
+    public R<Void> saveBatch(@RequestBody Map<String, Object> params) {
+        Long productId = Long.valueOf(params.get("productId").toString());
+        List<Long> programIds = ((List<?>) params.get("programIds")).stream()
+            .map(id -> Long.valueOf(id.toString()))
+            .toList();
+        return toAjax(productProgramLinkService.saveBatch(productId, programIds));
+    }
 }

+ 8 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ProductSpecsController.java

@@ -103,4 +103,12 @@ public class ProductSpecsController extends BaseController {
                           @PathVariable("ids") Long[] ids) {
         return toAjax(productSpecsService.deleteWithValidByIds(List.of(ids), true));
     }
+
+    /**
+     * 根据产品ID查询规格关联信息
+     */
+    @GetMapping("/product/{productId}")
+    public R<ProductSpecsVo> getByProductId(@PathVariable("productId") String productId) {
+        return R.ok(productSpecsService.queryByProductId(productId));
+    }
 }

+ 82 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductOperationInfo.java

@@ -0,0 +1,82 @@
+package org.dromara.product.domain;
+
+import org.dromara.common.tenant.core.TenantEntity;
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serial;
+
+/**
+ * 产品运营信息对象 product_operation_info
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("product_operation_info")
+public class ProductOperationInfo extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 产品ID,关联product_base.id
+     */
+    private Long productId;
+
+    /**
+     * 详细页标题
+     */
+    private String detailTitle;
+
+    /**
+     * 商品关键词,空格分隔
+     */
+    private String productKeywords;
+
+    /**
+     * 详细页描述
+     */
+    private String detailDescription;
+
+    /**
+     * 商品推荐标签,逗号分隔(recommend=推荐,hot=热销,special=特价,explosive=爆款)
+     */
+    private String productRecommend;
+
+    /**
+     * 适配价格区间,逗号分隔
+     */
+    private String priceInterval;
+
+    /**
+     * 标签TAG管理,逗号分隔
+     */
+    private String labelManage;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+
+}

+ 82 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductOperationInfoBo.java

@@ -0,0 +1,82 @@
+package org.dromara.product.domain.bo;
+
+import org.dromara.product.domain.ProductOperationInfo;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.common.core.validate.AddGroup;
+import org.dromara.common.core.validate.EditGroup;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import jakarta.validation.constraints.*;
+
+/**
+ * 产品运营信息业务对象 product_operation_info
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ProductOperationInfo.class, reverseConvertGenerate = false)
+public class ProductOperationInfoBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 产品ID,关联product_base.id
+     */
+    private Long productId;
+
+    /**
+     * 详细页标题
+     */
+//    @NotBlank(message = "详细页标题不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String detailTitle;
+
+    /**
+     * 商品关键词,空格分隔
+     */
+//    @NotBlank(message = "商品关键词,空格分隔不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String productKeywords;
+
+    /**
+     * 详细页描述
+     */
+//    @NotBlank(message = "详细页描述不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String detailDescription;
+
+    /**
+     * 商品推荐标签,逗号分隔(recommend=推荐,hot=热销,special=特价,explosive=爆款)
+     */
+//    @NotBlank(message = "商品推荐标签,逗号分隔(recommend=推荐,hot=热销,special=特价,explosive=爆款)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String productRecommend;
+
+    /**
+     * 适配价格区间,逗号分隔
+     */
+//    @NotBlank(message = "适配价格区间,逗号分隔不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String priceInterval;
+
+    /**
+     * 标签TAG管理,逗号分隔
+     */
+//    @NotBlank(message = "标签TAG管理,逗号分隔不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String labelManage;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+//    @NotBlank(message = "状态(0正常 1停用)不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String status;
+
+    /**
+     * 备注
+     */
+//    @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class })
+    private String remark;
+
+
+}

+ 94 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductOperationInfoVo.java

@@ -0,0 +1,94 @@
+package org.dromara.product.domain.vo;
+
+import org.dromara.product.domain.ProductOperationInfo;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import org.dromara.common.excel.annotation.ExcelDictFormat;
+import org.dromara.common.excel.convert.ExcelDictConvert;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+
+
+/**
+ * 产品运营信息视图对象 product_operation_info
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ProductOperationInfo.class)
+public class ProductOperationInfoVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    private Long id;
+
+    /**
+     * 产品ID,关联product_base.id
+     */
+    @ExcelProperty(value = "产品ID,关联product_base.id")
+    private Long productId;
+
+    /**
+     * 详细页标题
+     */
+    @ExcelProperty(value = "详细页标题")
+    private String detailTitle;
+
+    /**
+     * 商品关键词,空格分隔
+     */
+    @ExcelProperty(value = "商品关键词,空格分隔")
+    private String productKeywords;
+
+    /**
+     * 详细页描述
+     */
+    @ExcelProperty(value = "详细页描述")
+    private String detailDescription;
+
+    /**
+     * 商品推荐标签,逗号分隔(recommend=推荐,hot=热销,special=特价,explosive=爆款)
+     */
+    @ExcelProperty(value = "商品推荐标签,逗号分隔", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "r=ecommend=推荐,hot=热销,special=特价,explosive=爆款")
+    private String productRecommend;
+
+    /**
+     * 适配价格区间,逗号分隔
+     */
+    @ExcelProperty(value = "适配价格区间,逗号分隔")
+    private String priceInterval;
+
+    /**
+     * 标签TAG管理,逗号分隔
+     */
+    @ExcelProperty(value = "标签TAG管理,逗号分隔")
+    private String labelManage;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    @ExcelProperty(value = "状态", converter = ExcelDictConvert.class)
+    @ExcelDictFormat(readConverterExp = "0=正常,1=停用")
+    private String status;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+
+}

+ 85 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductOperationVo.java

@@ -0,0 +1,85 @@
+package org.dromara.product.domain.vo;
+
+import lombok.Data;
+import java.io.Serial;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+/**
+ * 商品运营VO
+ */
+@Data
+public class ProductOperationVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    private Long id;
+
+    /** 产品编号 */
+    private String productNo;
+
+    /** 商品名称 */
+    private String itemName;
+
+    /** 商品图片 */
+    private String productImage;
+
+    /** 品牌ID */
+    private Long brandId;
+
+    /** 品牌名称 */
+    private String brandName;
+
+    /** 规格 */
+    private String specification;
+
+    /** 顶级分类ID */
+    private Long topCategoryId;
+
+    /** 顶级分类名称 */
+    private String topCategoryName;
+
+    /** 中级分类ID */
+    private Long mediumCategoryId;
+
+    /** 中级分类名称 */
+    private String mediumCategoryName;
+
+    /** 底层分类ID */
+    private Long bottomCategoryId;
+
+    /** 底层分类名称 */
+    private String bottomCategoryName;
+
+    /** 单位名称 */
+    private String unitName;
+
+    /** 起订量 */
+    private Long minOrderQuantity;
+
+    /** 市场价 */
+    private BigDecimal marketPrice;
+
+    /** 会员价 */
+    private BigDecimal memberPrice;
+
+    /** 最低售价 */
+    private BigDecimal minSellingPrice;
+
+    /** 供应商数量 */
+    private Integer supplierCount;
+
+    /** 库存总数 */
+    private Long totalInventory;
+
+    /** 可销库存 */
+    private Long nowInventory;
+
+    /** 虚拟库存 */
+    private Long virtualInventory;
+
+    /** 商品状态 */
+    private String productStatus;
+}

+ 10 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductBaseMapper.java

@@ -6,6 +6,7 @@ import org.dromara.product.domain.ProductBase;
 import org.dromara.product.domain.bo.ProductBaseBo;
 import org.dromara.product.domain.bo.SiteProductBo;
 import org.dromara.product.domain.vo.ProductBaseVo;
+import org.dromara.product.domain.vo.ProductOperationVo;
 import org.dromara.product.domain.vo.RecommendProductVo;
 import org.dromara.product.domain.vo.SiteProductVo;
 import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
@@ -35,4 +36,13 @@ public interface ProductBaseMapper extends BaseMapperPlus<ProductBase, ProductBa
      * @return 推荐商品分页列表
      */
     Page<RecommendProductVo> selectRecommendProductPage(@Param("page") Page<RecommendProductVo> page, @Param("bo") ProductBaseBo bo);
+
+    /**
+     * 分页查询商品运营列表(联表查询)
+     *
+     * @param page 分页参数
+     * @param bo   查询条件
+     * @return 商品运营分页列表
+     */
+    Page<ProductOperationVo> selectProductOperationPage(@Param("page") Page<ProductOperationVo> page, @Param("bo") ProductBaseBo bo);
 }

+ 15 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ProductOperationInfoMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.product.mapper;
+
+import org.dromara.product.domain.ProductOperationInfo;
+import org.dromara.product.domain.vo.ProductOperationInfoVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 产品运营信息Mapper接口
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+public interface ProductOperationInfoMapper extends BaseMapperPlus<ProductOperationInfo, ProductOperationInfoVo> {
+
+}

+ 8 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductAssociateService.java

@@ -26,6 +26,14 @@ public interface IProductAssociateService extends IService<ProductAssociate>{
      */
     ProductAssociateVo queryById(Long id);
 
+    /**
+     * 根据产品ID查询关联信息
+     *
+     * @param productId 产品ID
+     * @return 产品关联
+     */
+    ProductAssociateVo queryByProductId(Long productId);
+
     /**
      * 分页查询产品关联列表
      *

+ 10 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductBaseService.java

@@ -3,6 +3,7 @@ package org.dromara.product.service;
 import com.baomidou.mybatisplus.extension.service.IService;
 import org.dromara.product.domain.ProductBase;
 import org.dromara.product.domain.vo.ProductBaseVo;
+import org.dromara.product.domain.vo.ProductOperationVo;
 import org.dromara.product.domain.vo.RecommendProductVo;
 import org.dromara.product.domain.vo.SiteProductVo;
 import org.dromara.product.domain.bo.ProductBaseBo;
@@ -95,4 +96,13 @@ public interface IProductBaseService extends IService<ProductBase>{
      * @return 推荐商品分页列表
      */
     TableDataInfo<RecommendProductVo> queryRecommendProductPageList(ProductBaseBo bo, PageQuery pageQuery);
+
+    /**
+     * 分页查询商品运营列表(联表查询)
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 商品运营分页列表
+     */
+    TableDataInfo<ProductOperationVo> queryProductOperationPageList(ProductBaseBo bo, PageQuery pageQuery);
 }

+ 78 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductOperationInfoService.java

@@ -0,0 +1,78 @@
+package org.dromara.product.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.product.domain.ProductOperationInfo;
+import org.dromara.product.domain.vo.ProductOperationInfoVo;
+import org.dromara.product.domain.bo.ProductOperationInfoBo;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 产品运营信息Service接口
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+public interface IProductOperationInfoService extends IService<ProductOperationInfo>{
+
+    /**
+     * 查询产品运营信息
+     *
+     * @param id 主键
+     * @return 产品运营信息
+     */
+    ProductOperationInfoVo queryById(Long id);
+
+    /**
+     * 根据产品ID查询运营信息
+     *
+     * @param productId 产品ID
+     * @return 产品运营信息
+     */
+    ProductOperationInfoVo queryByProductId(Long productId);
+
+    /**
+     * 分页查询产品运营信息列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 产品运营信息分页列表
+     */
+    TableDataInfo<ProductOperationInfoVo> queryPageList(ProductOperationInfoBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的产品运营信息列表
+     *
+     * @param bo 查询条件
+     * @return 产品运营信息列表
+     */
+    List<ProductOperationInfoVo> queryList(ProductOperationInfoBo bo);
+
+    /**
+     * 新增产品运营信息
+     *
+     * @param bo 产品运营信息
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(ProductOperationInfoBo bo);
+
+    /**
+     * 修改产品运营信息
+     *
+     * @param bo 产品运营信息
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ProductOperationInfoBo bo);
+
+    /**
+     * 校验并批量删除产品运营信息信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 17 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductProgramLinkService.java

@@ -67,4 +67,21 @@ public interface IProductProgramLinkService extends IService<ProductProgramLink>
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 根据产品ID查询已关联的方案ID列表
+     *
+     * @param productId 产品ID
+     * @return 方案ID列表
+     */
+    List<Long> queryProgramIdsByProductId(Long productId);
+
+    /**
+     * 批量保存产品方案关联(先删后增)
+     *
+     * @param productId  产品ID
+     * @param programIds 方案ID列表
+     * @return 是否成功
+     */
+    Boolean saveBatch(Long productId, List<Long> programIds);
 }

+ 8 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IProductSpecsService.java

@@ -67,4 +67,12 @@ public interface IProductSpecsService extends IService<ProductSpecs>{
      * @return 是否删除成功
      */
     Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 根据产品ID查询规格关联信息
+     *
+     * @param productId 产品ID
+     * @return 规格关联信息
+     */
+    ProductSpecsVo queryByProductId(String productId);
 }

+ 136 - 9
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductAssociateServiceImpl.java

@@ -20,6 +20,9 @@ import org.dromara.product.service.IProductAssociateService;
 import java.util.List;
 import java.util.Map;
 import java.util.Collection;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Arrays;
 
 /**
  * 产品关联Service业务层处理
@@ -45,6 +48,20 @@ public class ProductAssociateServiceImpl  extends ServiceImpl<ProductAssociateMa
         return baseMapper.selectVoById(id);
     }
 
+    /**
+     * 根据产品ID查询关联信息
+     *
+     * @param productId 产品ID
+     * @return 产品关联
+     */
+    @Override
+    public ProductAssociateVo queryByProductId(Long productId){
+        LambdaQueryWrapper<ProductAssociate> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductAssociate::getProductId, productId);
+        lqw.last("LIMIT 1"); // 防止多条记录报错
+        return baseMapper.selectVoOne(lqw);
+    }
+
     /**
      * 分页查询产品关联列表
      *
@@ -91,13 +108,20 @@ public class ProductAssociateServiceImpl  extends ServiceImpl<ProductAssociateMa
      */
     @Override
     public Boolean insertByBo(ProductAssociateBo bo) {
-        ProductAssociate add = MapstructUtils.convert(bo, ProductAssociate.class);
-        validEntityBeforeSave(add);
-        boolean flag = baseMapper.insert(add) > 0;
-        if (flag) {
-            bo.setId(add.getId());
+        // 保存当前商品的关联
+        boolean result = saveOrUpdateAssociate(bo.getProductId(), bo.getProductIds(), bo.getIsUnidirectional(), bo.getRelatedTitle(), bo.getRemark());
+        
+        // 如果是双向关联(isUnidirectional = "0"),给被关联的商品也添加关联
+        if ("0".equals(bo.getIsUnidirectional()) && StringUtils.isNotBlank(bo.getProductIds())) {
+            String[] targetIds = bo.getProductIds().split(",");
+            for (String targetId : targetIds) {
+                if (StringUtils.isNotBlank(targetId)) {
+                    addBidirectionalAssociate(Long.parseLong(targetId.trim()), bo.getProductId());
+                }
+            }
         }
-        return flag;
+        
+        return result;
     }
 
     /**
@@ -108,9 +132,112 @@ public class ProductAssociateServiceImpl  extends ServiceImpl<ProductAssociateMa
      */
     @Override
     public Boolean updateByBo(ProductAssociateBo bo) {
-        ProductAssociate update = MapstructUtils.convert(bo, ProductAssociate.class);
-        validEntityBeforeSave(update);
-        return baseMapper.updateById(update) > 0;
+        // 先获取旧的关联数据,用于处理双向关联的移除
+        ProductAssociate oldAssociate = baseMapper.selectById(bo.getId());
+        
+        // 保存当前商品的关联
+        boolean result = saveOrUpdateAssociate(bo.getProductId(), bo.getProductIds(), bo.getIsUnidirectional(), bo.getRelatedTitle(), bo.getRemark());
+        
+        // 如果是双向关联,处理新增和移除
+        if ("0".equals(bo.getIsUnidirectional())) {
+            Set<String> oldIds = new HashSet<>();
+            Set<String> newIds = new HashSet<>();
+            
+            if (oldAssociate != null && StringUtils.isNotBlank(oldAssociate.getProductIds())) {
+                oldIds.addAll(Arrays.asList(oldAssociate.getProductIds().split(",")));
+            }
+            if (StringUtils.isNotBlank(bo.getProductIds())) {
+                newIds.addAll(Arrays.asList(bo.getProductIds().split(",")));
+            }
+            
+            // 新增的关联:在newIds中但不在oldIds中
+            for (String newId : newIds) {
+                if (StringUtils.isNotBlank(newId) && !oldIds.contains(newId)) {
+                    addBidirectionalAssociate(Long.parseLong(newId.trim()), bo.getProductId());
+                }
+            }
+            
+            // 移除的关联:在oldIds中但不在newIds中
+            for (String oldId : oldIds) {
+                if (StringUtils.isNotBlank(oldId) && !newIds.contains(oldId)) {
+                    removeBidirectionalAssociate(Long.parseLong(oldId.trim()), bo.getProductId());
+                }
+            }
+        }
+        
+        return result;
+    }
+    
+    /**
+     * 保存或更新关联记录
+     */
+    private boolean saveOrUpdateAssociate(Long productId, String productIds, String isUnidirectional, String relatedTitle, String remark) {
+        LambdaQueryWrapper<ProductAssociate> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductAssociate::getProductId, productId);
+        ProductAssociate existing = baseMapper.selectOne(lqw);
+        
+        if (existing != null) {
+            existing.setProductIds(productIds);
+            existing.setIsUnidirectional(isUnidirectional);
+            existing.setRelatedTitle(relatedTitle);
+            existing.setRemark(remark);
+            return baseMapper.updateById(existing) > 0;
+        }
+        
+        ProductAssociate add = new ProductAssociate();
+        add.setProductId(productId);
+        add.setProductIds(productIds);
+        add.setIsUnidirectional(isUnidirectional);
+        add.setRelatedTitle(relatedTitle);
+        add.setRemark(remark);
+        return baseMapper.insert(add) > 0;
+    }
+    
+    /**
+     * 给目标商品添加双向关联(把sourceProductId加到目标商品的关联列表中)
+     */
+    private void addBidirectionalAssociate(Long targetProductId, Long sourceProductId) {
+        LambdaQueryWrapper<ProductAssociate> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductAssociate::getProductId, targetProductId);
+        ProductAssociate targetAssociate = baseMapper.selectOne(lqw);
+        
+        String sourceIdStr = String.valueOf(sourceProductId);
+        
+        if (targetAssociate != null) {
+            // 已有记录,追加关联
+            Set<String> ids = new HashSet<>();
+            if (StringUtils.isNotBlank(targetAssociate.getProductIds())) {
+                ids.addAll(Arrays.asList(targetAssociate.getProductIds().split(",")));
+            }
+            if (!ids.contains(sourceIdStr)) {
+                ids.add(sourceIdStr);
+                targetAssociate.setProductIds(String.join(",", ids));
+                baseMapper.updateById(targetAssociate);
+            }
+        } else {
+            // 没有记录,新建
+            ProductAssociate newAssociate = new ProductAssociate();
+            newAssociate.setProductId(targetProductId);
+            newAssociate.setProductIds(sourceIdStr);
+            newAssociate.setIsUnidirectional("0"); // 双向
+            baseMapper.insert(newAssociate);
+        }
+    }
+    
+    /**
+     * 从目标商品移除双向关联(把sourceProductId从目标商品的关联列表中移除)
+     */
+    private void removeBidirectionalAssociate(Long targetProductId, Long sourceProductId) {
+        LambdaQueryWrapper<ProductAssociate> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductAssociate::getProductId, targetProductId);
+        ProductAssociate targetAssociate = baseMapper.selectOne(lqw);
+        
+        if (targetAssociate != null && StringUtils.isNotBlank(targetAssociate.getProductIds())) {
+            Set<String> ids = new HashSet<>(Arrays.asList(targetAssociate.getProductIds().split(",")));
+            ids.remove(String.valueOf(sourceProductId));
+            targetAssociate.setProductIds(String.join(",", ids));
+            baseMapper.updateById(targetAssociate);
+        }
     }
 
     /**

+ 14 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductBaseServiceImpl.java

@@ -20,6 +20,7 @@ import org.springframework.transaction.annotation.Transactional;
 import org.dromara.product.domain.bo.ProductBaseBo;
 import org.dromara.product.domain.bo.SiteProductBo;
 import org.dromara.product.domain.vo.ProductBaseVo;
+import org.dromara.product.domain.vo.ProductOperationVo;
 import org.dromara.product.domain.vo.RecommendProductVo;
 import org.dromara.product.domain.vo.SiteProductVo;
 import org.dromara.product.domain.ProductBase;
@@ -740,4 +741,17 @@ public class ProductBaseServiceImpl  extends ServiceImpl<ProductBaseMapper, Prod
         Page<RecommendProductVo> page = baseMapper.selectRecommendProductPage(pageQuery.build(), bo);
         return TableDataInfo.build(page);
     }
+
+    /**
+     * 分页查询商品运营列表(联表查询)
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 商品运营分页列表
+     */
+    @Override
+    public TableDataInfo<ProductOperationVo> queryProductOperationPageList(ProductBaseBo bo, PageQuery pageQuery) {
+        Page<ProductOperationVo> page = baseMapper.selectProductOperationPage(pageQuery.build(), bo);
+        return TableDataInfo.build(page);
+    }
 }

+ 154 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductOperationInfoServiceImpl.java

@@ -0,0 +1,154 @@
+package org.dromara.product.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.dromara.product.domain.bo.ProductOperationInfoBo;
+import org.dromara.product.domain.vo.ProductOperationInfoVo;
+import org.dromara.product.domain.ProductOperationInfo;
+import org.dromara.product.mapper.ProductOperationInfoMapper;
+import org.dromara.product.service.IProductOperationInfoService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Collection;
+
+/**
+ * 产品运营信息Service业务层处理
+ *
+ * @author LionLi
+ * @date 2026-01-08
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ProductOperationInfoServiceImpl  extends ServiceImpl<ProductOperationInfoMapper, ProductOperationInfo> implements IProductOperationInfoService {
+
+    private final ProductOperationInfoMapper baseMapper;
+
+    /**
+     * 查询产品运营信息
+     *
+     * @param id 主键
+     * @return 产品运营信息
+     */
+    @Override
+    public ProductOperationInfoVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 根据产品ID查询运营信息
+     *
+     * @param productId 产品ID
+     * @return 产品运营信息
+     */
+    @Override
+    public ProductOperationInfoVo queryByProductId(Long productId){
+        LambdaQueryWrapper<ProductOperationInfo> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductOperationInfo::getProductId, productId);
+        return baseMapper.selectVoOne(lqw);
+    }
+
+    /**
+     * 分页查询产品运营信息列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 产品运营信息分页列表
+     */
+    @Override
+    public TableDataInfo<ProductOperationInfoVo> queryPageList(ProductOperationInfoBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ProductOperationInfo> lqw = buildQueryWrapper(bo);
+        Page<ProductOperationInfoVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的产品运营信息列表
+     *
+     * @param bo 查询条件
+     * @return 产品运营信息列表
+     */
+    @Override
+    public List<ProductOperationInfoVo> queryList(ProductOperationInfoBo bo) {
+        LambdaQueryWrapper<ProductOperationInfo> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ProductOperationInfo> buildQueryWrapper(ProductOperationInfoBo bo) {
+        Map<String, Object> params = bo.getParams();
+        LambdaQueryWrapper<ProductOperationInfo> lqw = Wrappers.lambdaQuery();
+        lqw.orderByAsc(ProductOperationInfo::getId);
+        lqw.eq(bo.getProductId() != null, ProductOperationInfo::getProductId, bo.getProductId());
+        lqw.eq(StringUtils.isNotBlank(bo.getDetailTitle()), ProductOperationInfo::getDetailTitle, bo.getDetailTitle());
+        lqw.eq(StringUtils.isNotBlank(bo.getProductKeywords()), ProductOperationInfo::getProductKeywords, bo.getProductKeywords());
+        lqw.eq(StringUtils.isNotBlank(bo.getDetailDescription()), ProductOperationInfo::getDetailDescription, bo.getDetailDescription());
+        lqw.eq(StringUtils.isNotBlank(bo.getProductRecommend()), ProductOperationInfo::getProductRecommend, bo.getProductRecommend());
+        lqw.eq(StringUtils.isNotBlank(bo.getPriceInterval()), ProductOperationInfo::getPriceInterval, bo.getPriceInterval());
+        lqw.eq(StringUtils.isNotBlank(bo.getLabelManage()), ProductOperationInfo::getLabelManage, bo.getLabelManage());
+        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), ProductOperationInfo::getStatus, bo.getStatus());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), ProductOperationInfo::getPlatformCode, bo.getPlatformCode());
+        return lqw;
+    }
+
+    /**
+     * 新增产品运营信息
+     *
+     * @param bo 产品运营信息
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(ProductOperationInfoBo bo) {
+        ProductOperationInfo add = MapstructUtils.convert(bo, ProductOperationInfo.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改产品运营信息
+     *
+     * @param bo 产品运营信息
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ProductOperationInfoBo bo) {
+        ProductOperationInfo update = MapstructUtils.convert(bo, ProductOperationInfo.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ProductOperationInfo entity){
+        //TODO 做一些数据校验,如唯一约束
+    }
+
+    /**
+     * 校验并批量删除产品运营信息信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            //TODO 做一些业务上的校验,判断是否需要校验
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 34 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductProgramLinkServiceImpl.java

@@ -132,4 +132,38 @@ public class ProductProgramLinkServiceImpl  extends ServiceImpl<ProductProgramLi
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    /**
+     * 根据产品ID查询已关联的方案ID列表
+     */
+    @Override
+    public List<Long> queryProgramIdsByProductId(Long productId) {
+        LambdaQueryWrapper<ProductProgramLink> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductProgramLink::getProductId, productId);
+        lqw.select(ProductProgramLink::getProgramId);
+        List<ProductProgramLink> list = baseMapper.selectList(lqw);
+        return list.stream().map(ProductProgramLink::getProgramId).toList();
+    }
+
+    /**
+     * 批量保存产品方案关联(先删后增)
+     */
+    @Override
+    public Boolean saveBatch(Long productId, List<Long> programIds) {
+        // 先删除该产品的所有关联
+        LambdaQueryWrapper<ProductProgramLink> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductProgramLink::getProductId, productId);
+        baseMapper.delete(lqw);
+        
+        // 再批量新增
+        if (programIds != null && !programIds.isEmpty()) {
+            for (Long programId : programIds) {
+                ProductProgramLink link = new ProductProgramLink();
+                link.setProductId(productId);
+                link.setProgramId(programId);
+                baseMapper.insert(link);
+            }
+        }
+        return true;
+    }
 }

+ 127 - 9
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ProductSpecsServiceImpl.java

@@ -20,6 +20,9 @@ import org.dromara.product.service.IProductSpecsService;
 import java.util.List;
 import java.util.Map;
 import java.util.Collection;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Arrays;
 
 /**
  * 产品规格关联Service业务层处理
@@ -90,13 +93,20 @@ public class ProductSpecsServiceImpl  extends ServiceImpl<ProductSpecsMapper, Pr
      */
     @Override
     public Boolean insertByBo(ProductSpecsBo bo) {
-        ProductSpecs add = MapstructUtils.convert(bo, ProductSpecs.class);
-        validEntityBeforeSave(add);
-        boolean flag = baseMapper.insert(add) > 0;
-        if (flag) {
-            bo.setId(add.getId());
+        // 保存当前商品的关联
+        boolean result = saveOrUpdateSpecs(bo.getProductId(), bo.getSpecsProductIds(), bo.getIsUnidirectional(), bo.getRemark());
+        
+        // 如果是双向关联(isUnidirectional = "0"),给被关联的商品也添加关联
+        if ("0".equals(bo.getIsUnidirectional()) && StringUtils.isNotBlank(bo.getSpecsProductIds())) {
+            String[] targetIds = bo.getSpecsProductIds().split(",");
+            for (String targetId : targetIds) {
+                if (StringUtils.isNotBlank(targetId)) {
+                    addBidirectionalSpecs(targetId.trim(), bo.getProductId());
+                }
+            }
         }
-        return flag;
+        
+        return result;
     }
 
     /**
@@ -107,9 +117,106 @@ public class ProductSpecsServiceImpl  extends ServiceImpl<ProductSpecsMapper, Pr
      */
     @Override
     public Boolean updateByBo(ProductSpecsBo bo) {
-        ProductSpecs update = MapstructUtils.convert(bo, ProductSpecs.class);
-        validEntityBeforeSave(update);
-        return baseMapper.updateById(update) > 0;
+        // 先获取旧的关联数据
+        ProductSpecs oldSpecs = baseMapper.selectById(bo.getId());
+        
+        // 保存当前商品的关联
+        boolean result = saveOrUpdateSpecs(bo.getProductId(), bo.getSpecsProductIds(), bo.getIsUnidirectional(), bo.getRemark());
+        
+        // 如果是双向关联,处理新增和移除
+        if ("0".equals(bo.getIsUnidirectional())) {
+            Set<String> oldIds = new HashSet<>();
+            Set<String> newIds = new HashSet<>();
+            
+            if (oldSpecs != null && StringUtils.isNotBlank(oldSpecs.getSpecsProductIds())) {
+                oldIds.addAll(Arrays.asList(oldSpecs.getSpecsProductIds().split(",")));
+            }
+            if (StringUtils.isNotBlank(bo.getSpecsProductIds())) {
+                newIds.addAll(Arrays.asList(bo.getSpecsProductIds().split(",")));
+            }
+            
+            // 新增的关联
+            for (String newId : newIds) {
+                if (StringUtils.isNotBlank(newId) && !oldIds.contains(newId)) {
+                    addBidirectionalSpecs(newId.trim(), bo.getProductId());
+                }
+            }
+            
+            // 移除的关联
+            for (String oldId : oldIds) {
+                if (StringUtils.isNotBlank(oldId) && !newIds.contains(oldId)) {
+                    removeBidirectionalSpecs(oldId.trim(), bo.getProductId());
+                }
+            }
+        }
+        
+        return result;
+    }
+    
+    /**
+     * 保存或更新规格关联记录
+     */
+    private boolean saveOrUpdateSpecs(String productId, String specsProductIds, String isUnidirectional, String remark) {
+        LambdaQueryWrapper<ProductSpecs> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductSpecs::getProductId, productId);
+        ProductSpecs existing = baseMapper.selectOne(lqw);
+        
+        if (existing != null) {
+            existing.setSpecsProductIds(specsProductIds);
+            existing.setIsUnidirectional(isUnidirectional);
+            existing.setRemark(remark);
+            return baseMapper.updateById(existing) > 0;
+        }
+        
+        ProductSpecs add = new ProductSpecs();
+        add.setProductId(productId);
+        add.setSpecsProductIds(specsProductIds);
+        add.setIsUnidirectional(isUnidirectional);
+        add.setRemark(remark);
+        return baseMapper.insert(add) > 0;
+    }
+    
+    /**
+     * 给目标商品添加双向规格关联
+     */
+    private void addBidirectionalSpecs(String targetProductId, String sourceProductId) {
+        LambdaQueryWrapper<ProductSpecs> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductSpecs::getProductId, targetProductId);
+        ProductSpecs targetSpecs = baseMapper.selectOne(lqw);
+        
+        if (targetSpecs != null) {
+            Set<String> ids = new HashSet<>();
+            if (StringUtils.isNotBlank(targetSpecs.getSpecsProductIds())) {
+                ids.addAll(Arrays.asList(targetSpecs.getSpecsProductIds().split(",")));
+            }
+            if (!ids.contains(sourceProductId)) {
+                ids.add(sourceProductId);
+                targetSpecs.setSpecsProductIds(String.join(",", ids));
+                baseMapper.updateById(targetSpecs);
+            }
+        } else {
+            ProductSpecs newSpecs = new ProductSpecs();
+            newSpecs.setProductId(targetProductId);
+            newSpecs.setSpecsProductIds(sourceProductId);
+            newSpecs.setIsUnidirectional("0");
+            baseMapper.insert(newSpecs);
+        }
+    }
+    
+    /**
+     * 从目标商品移除双向规格关联
+     */
+    private void removeBidirectionalSpecs(String targetProductId, String sourceProductId) {
+        LambdaQueryWrapper<ProductSpecs> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductSpecs::getProductId, targetProductId);
+        ProductSpecs targetSpecs = baseMapper.selectOne(lqw);
+        
+        if (targetSpecs != null && StringUtils.isNotBlank(targetSpecs.getSpecsProductIds())) {
+            Set<String> ids = new HashSet<>(Arrays.asList(targetSpecs.getSpecsProductIds().split(",")));
+            ids.remove(sourceProductId);
+            targetSpecs.setSpecsProductIds(String.join(",", ids));
+            baseMapper.updateById(targetSpecs);
+        }
     }
 
     /**
@@ -133,4 +240,15 @@ public class ProductSpecsServiceImpl  extends ServiceImpl<ProductSpecsMapper, Pr
         }
         return baseMapper.deleteByIds(ids) > 0;
     }
+
+    /**
+     * 根据产品ID查询规格关联信息
+     */
+    @Override
+    public ProductSpecsVo queryByProductId(String productId) {
+        LambdaQueryWrapper<ProductSpecs> lqw = Wrappers.lambdaQuery();
+        lqw.eq(ProductSpecs::getProductId, productId);
+        lqw.last("LIMIT 1");
+        return baseMapper.selectVoOne(lqw);
+    }
 }

+ 58 - 0
ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductBaseMapper.xml

@@ -135,4 +135,62 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         ORDER BY b.recommended_product_order ASC, b.create_time DESC
     </select>
 
+    <!-- 商品运营列表查询(联表查询) -->
+    <select id="selectProductOperationPage" resultType="org.dromara.product.domain.vo.ProductOperationVo">
+        SELECT
+            b.id,
+            b.product_no AS productNo,
+            b.item_name AS itemName,
+            b.product_image AS productImage,
+            b.brand_id AS brandId,
+            br.brand_name AS brandName,
+            b.top_category_id AS topCategoryId,
+            tc.category_name AS topCategoryName,
+            b.medium_category_id AS mediumCategoryId,
+            mc.category_name AS mediumCategoryName,
+            b.bottom_category_id AS bottomCategoryId,
+            bc.category_name AS bottomCategoryName,
+            u.unit_name AS unitName,
+            p.min_order_quantity AS minOrderQuantity,
+            p.market_price AS marketPrice,
+            p.member_price AS memberPrice,
+            p.min_selling_price AS minSellingPrice,
+            p.total_inventory AS totalInventory,
+            p.now_inventory AS nowInventory,
+            p.virtual_inventory AS virtualInventory,
+            b.product_status AS productStatus
+        FROM product_base b
+        LEFT JOIN product_price_inventory p ON b.id = p.product_id AND p.del_flag = '0'
+        LEFT JOIN product_brand br ON b.brand_id = br.id AND br.del_flag = '0'
+        LEFT JOIN product_category tc ON b.top_category_id = tc.id AND tc.del_flag = '0'
+        LEFT JOIN product_category mc ON b.medium_category_id = mc.id AND mc.del_flag = '0'
+        LEFT JOIN product_category bc ON b.bottom_category_id = bc.id AND bc.del_flag = '0'
+        LEFT JOIN product_unit u ON b.unit_id = u.id AND u.del_flag = '0'
+        <where>
+            b.del_flag = '0'
+            <if test="bo.productNo != null and bo.productNo != ''">
+                AND b.product_no LIKE CONCAT('%', #{bo.productNo}, '%')
+            </if>
+            <if test="bo.itemName != null and bo.itemName != ''">
+                AND b.item_name LIKE CONCAT('%', #{bo.itemName}, '%')
+            </if>
+            <if test="bo.brandId != null">
+                AND b.brand_id = #{bo.brandId}
+            </if>
+            <if test="bo.productStatus != null and bo.productStatus != ''">
+                AND b.product_status = #{bo.productStatus}
+            </if>
+            <if test="bo.topCategoryId != null">
+                AND b.top_category_id = #{bo.topCategoryId}
+            </if>
+            <if test="bo.mediumCategoryId != null">
+                AND b.medium_category_id = #{bo.mediumCategoryId}
+            </if>
+            <if test="bo.bottomCategoryId != null">
+                AND b.bottom_category_id = #{bo.bottomCategoryId}
+            </if>
+        </where>
+        ORDER BY b.create_time DESC
+    </select>
+
 </mapper>

+ 7 - 0
ruoyi-modules/ruoyi-product/src/main/resources/mapper/product/ProductOperationInfoMapper.xml

@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="org.dromara.product.mapper.ProductOperationInfoMapper">
+
+</mapper>

+ 26 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteCreditLevelServiceImpl.java

@@ -0,0 +1,26 @@
+package org.dromara.system.dubbo;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboService;
+import org.dromara.system.api.RemoteCreditLevelService;
+import org.dromara.system.service.ICreditLevelService;
+import org.springframework.stereotype.Service;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * 信用等级远程服务实现
+ */
+@RequiredArgsConstructor
+@Service
+@DubboService
+public class RemoteCreditLevelServiceImpl implements RemoteCreditLevelService {
+
+    private final ICreditLevelService creditLevelService;
+
+    @Override
+    public Map<Long, String> selectCreditLevelNameByIds(Set<Long> ids) {
+        return creditLevelService.selectCreditLevelNameByIds(ids);
+    }
+}

+ 10 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/ICreditLevelService.java

@@ -10,6 +10,8 @@ import org.dromara.common.mybatis.core.page.PageQuery;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * 信用等级配置Service接口
@@ -27,6 +29,14 @@ public interface ICreditLevelService extends IService<CreditLevel> {
      */
     CreditLevelVo queryById(Long id);
 
+    /**
+     * 根据ID批量查询信用等级名称
+     *
+     * @param ids 信用等级ID集合
+     * @return Map<ID, 名称>
+     */
+    Map<Long, String> selectCreditLevelNameByIds(Set<Long> ids);
+
     /**
      * 分页查询信用等级配置列表
      *

+ 22 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/CreditLevelServiceImpl.java

@@ -20,8 +20,11 @@ import org.springframework.stereotype.Service;
 
 import java.time.Duration;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * 信用等级配置Service业务层处理
@@ -49,6 +52,25 @@ public class CreditLevelServiceImpl extends ServiceImpl<CreditLevelMapper, Credi
         return baseMapper.selectVoById(id);
     }
 
+    /**
+     * 根据ID批量查询信用等级名称
+     *
+     * @param ids 信用等级ID集合
+     * @return Map<ID, 名称>
+     */
+    @Override
+    public Map<Long, String> selectCreditLevelNameByIds(Set<Long> ids) {
+        if (ids == null || ids.isEmpty()) {
+            return Collections.emptyMap();
+        }
+        List<CreditLevel> list = baseMapper.selectBatchIds(ids);
+        return list.stream().collect(Collectors.toMap(
+            CreditLevel::getId,
+            CreditLevel::getCreditLevelName,
+            (e, r) -> e
+        ));
+    }
+
     /**
      * 分页查询信用等级配置列表
      *