Pārlūkot izejas kodu

Merge branch 'zl'

林小张 2 mēneši atpakaļ
vecāks
revīzija
d120cb32d6
28 mainītis faili ar 1694 papildinājumiem un 10 dzēšanām
  1. 10 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteComStaffService.java
  2. 64 0
      ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteComStaffVo.java
  3. 6 1
      ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java
  4. 69 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/pc/controller/PcStatementDetailController.java
  5. 58 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/pc/controller/PcStatementInvoiceController.java
  6. 147 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/pc/controller/PcStatementOrderController.java
  7. 103 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/pc/controller/PcStatementProductController.java
  8. 10 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/service/IStatementOrderService.java
  9. 14 0
      ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/service/impl/StatementOrderServiceImpl.java
  10. 118 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/pc/controller/PcServicePersonController.java
  11. 171 0
      ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/pc/controller/PcOrderController.java
  12. 144 0
      ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/pc/controller/PcOrderReturnController.java
  13. 10 2
      ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/service/impl/OrderMainServiceImpl.java
  14. 104 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ServiceCaseController.java
  15. 20 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductRecommendLink.java
  16. 106 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ServiceCase.java
  17. 20 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductRecommendLinkBo.java
  18. 98 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ServiceCaseBo.java
  19. 24 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductRecommendLinkVo.java
  20. 104 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ServiceCaseVo.java
  21. 15 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/mapper/ServiceCaseMapper.java
  22. 70 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IServiceCaseService.java
  23. 137 0
      ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ServiceCaseServiceImpl.java
  24. 6 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/ComStaffVo.java
  25. 9 1
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteComStaffServiceImpl.java
  26. 8 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/IComStaffService.java
  27. 44 0
      ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/ComStaffServiceImpl.java
  28. 5 5
      ruoyi-modules/ruoyi-system/src/main/resources/application.yml

+ 10 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/RemoteComStaffService.java

@@ -1,5 +1,8 @@
 package org.dromara.system.api;
 
+import org.dromara.system.api.domain.vo.RemoteComStaffVo;
+
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -9,4 +12,11 @@ public interface RemoteComStaffService {
 
     Map<String,String> selectStaffNameAndCode();
 
+    /**
+     * 根据人员ID批量查询人员完整信息
+     * @param ids 人员ID集合
+     * @return 人员信息列表
+     */
+    List<RemoteComStaffVo> selectStaffByIds(Set<Long> ids);
+
 }

+ 64 - 0
ruoyi-api/ruoyi-api-system/src/main/java/org/dromara/system/api/domain/vo/RemoteComStaffVo.java

@@ -0,0 +1,64 @@
+package org.dromara.system.api.domain.vo;
+
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 远程调用 - 人员信息视图对象
+ *
+ * @author Claude
+ * @date 2026-01-28
+ */
+@Data
+public class RemoteComStaffVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 人员ID
+     */
+    private Long staffId;
+
+    /**
+     * 人员编码
+     */
+    private String staffCode;
+
+    /**
+     * 姓名
+     */
+    private String staffName;
+
+    /**
+     * 所属部门ID
+     */
+    private Long deptId;
+
+    /**
+     * 所属部门名称
+     */
+    private String deptName;
+
+    /**
+     * 联系电话
+     */
+    private String phone;
+
+    /**
+     * 岗位ID
+     */
+    private Long postId;
+
+    /**
+     * 岗位名称
+     */
+    private String postName;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+}

+ 6 - 1
ruoyi-common/ruoyi-common-satoken/src/main/java/org/dromara/common/satoken/utils/LoginHelper.java

@@ -87,7 +87,12 @@ public class LoginHelper {
      * 获取用户id
      */
     public static Long getUserId() {
-        return Convert.toLong(getExtra(USER_KEY));
+        // ⚠️⚠️⚠️ 临时测试代码,登录功能完成后必须删除 ⚠️⚠️⚠️
+        // TODO: 临时返回固定的客户ID用于测试PC端接口,登录功能完成后删除此行
+//         return 2009201047173152769L;
+
+        // 原来的代码(登录功能完成后恢复)
+         return Convert.toLong(getExtra(USER_KEY));
     }
 
     /**

+ 69 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/pc/controller/PcStatementDetailController.java

@@ -0,0 +1,69 @@
+package org.dromara.bill.pc.controller;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.validation.constraints.*;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.bill.domain.bo.StatementDetailBo;
+import org.dromara.bill.domain.vo.StatementDetailVo;
+import org.dromara.bill.domain.vo.StatementOrderVo;
+import org.dromara.bill.service.IStatementDetailService;
+import org.dromara.bill.service.IStatementOrderService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * PC端 - 对账单明细管理
+ * 前端访问路由地址为:/pc/enterprise/statementDetail
+ *
+ * @author Claude
+ * @date 2026-01-29
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/pc/enterprise/statementDetail")
+public class PcStatementDetailController extends BaseController {
+
+    private final IStatementDetailService statementDetailService;
+    private final IStatementOrderService statementOrderService;
+
+    /**
+     * 查询对账单明细列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo<StatementDetailVo> list(StatementDetailBo bo, PageQuery pageQuery) {
+        if (bo.getStatementOrderId() != null) {
+            StatementOrderVo statement = statementOrderService.queryById(bo.getStatementOrderId());
+            if (statement != null) {
+                Long customerId = LoginHelper.getUserId();
+                if (!customerId.equals(statement.getCustomerId())) {
+                    throw new IllegalArgumentException("无权访问该对账单明细");
+                }
+            }
+        }
+        return statementDetailService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取对账单明细详细信息
+     */
+    @GetMapping("/{id}")
+    public R<StatementDetailVo> getInfo(@NotNull(message = "主键不能为空")
+                                        @PathVariable("id") Long id) {
+        StatementDetailVo vo = statementDetailService.queryById(id);
+        if (vo != null && vo.getStatementOrderId() != null) {
+            StatementOrderVo statement = statementOrderService.queryById(vo.getStatementOrderId());
+            if (statement != null) {
+                Long customerId = LoginHelper.getUserId();
+                if (!customerId.equals(statement.getCustomerId())) {
+                    return R.fail("无权访问该对账单明细");
+                }
+            }
+        }
+        return R.ok(vo);
+    }
+}

+ 58 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/pc/controller/PcStatementInvoiceController.java

@@ -0,0 +1,58 @@
+package org.dromara.bill.pc.controller;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.validation.constraints.*;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.bill.domain.bo.StatementInvoiceBo;
+import org.dromara.bill.domain.vo.StatementInvoiceVo;
+import org.dromara.bill.service.IStatementInvoiceService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+/**
+ * PC端 - 对账单发票管理
+ * 前端访问路由地址为:/pc/enterprise/statementInvoice
+ *
+ * @author Claude
+ * @date 2026-01-29
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/pc/enterprise/statementInvoice")
+public class PcStatementInvoiceController extends BaseController {
+
+    private final IStatementInvoiceService statementInvoiceService;
+
+    /**
+     * 查询对账单发票列表
+     * PC端用户只能查询自己企业的对账单发票
+     */
+    @GetMapping("/list")
+    public TableDataInfo<StatementInvoiceVo> list(StatementInvoiceBo bo, PageQuery pageQuery) {
+        Long customerId = LoginHelper.getUserId();
+        bo.setCustomerId(customerId);
+        return statementInvoiceService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取对账单发票详细信息
+     * PC端用户只能查询自己企业的对账单发票
+     */
+    @GetMapping("/{id}")
+    public R<StatementInvoiceVo> getInfo(@NotNull(message = "主键不能为空")
+                                         @PathVariable("id") Long id) {
+        StatementInvoiceVo vo = statementInvoiceService.queryById(id);
+        if (vo != null) {
+            Long customerId = LoginHelper.getUserId();
+            if (!customerId.equals(vo.getCustomerId())) {
+                return R.fail("无权访问该对账单发票");
+            }
+        }
+        return R.ok(vo);
+    }
+}

+ 147 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/pc/controller/PcStatementOrderController.java

@@ -0,0 +1,147 @@
+package org.dromara.bill.pc.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.RequiredArgsConstructor;
+import jakarta.validation.constraints.*;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.bill.domain.StatementOrder;
+import org.dromara.bill.domain.bo.StatementOrderBo;
+import org.dromara.bill.domain.vo.StatementOrderVo;
+import org.dromara.bill.domain.vo.StatementDetailVo;
+import org.dromara.bill.service.IStatementOrderService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * PC端 - 对账单管理
+ * 前端访问路由地址为:/pc/enterprise/statement
+ *
+ * @author Claude
+ * @date 2026-01-29
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/pc/enterprise/statement")
+public class PcStatementOrderController extends BaseController {
+
+    private final IStatementOrderService statementOrderService;
+
+    /**
+     * 查询当前企业的对账单列表
+     * PC端用户只能查询自己企业的对账单
+     * 只允许查询状态为 1(待对账)、2(已对账)、3(驳回) 的对账单
+     * 不允许查询状态为 0(待确认)、4(作废) 的对账单
+     */
+    @GetMapping("/list")
+    public TableDataInfo<StatementOrderVo> list(StatementOrderBo bo, PageQuery pageQuery) {
+        Long customerId = LoginHelper.getUserId();
+
+        // PC端权限控制:强制只查询状态为 1、2、3 的对账单
+        LambdaQueryWrapper<StatementOrder> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(StatementOrder::getCustomerId, customerId);
+        wrapper.in(StatementOrder::getStatementStatus, Arrays.asList("1", "2", "3"));
+
+        // 如果用户指定了状态,且状态在允许范围内,则使用用户指定的状态
+        String statementStatus = bo.getStatementStatus();
+        if (statementStatus != null && !statementStatus.isEmpty()) {
+            if ("1".equals(statementStatus) || "2".equals(statementStatus) || "3".equals(statementStatus)) {
+                // 用户指定的状态在允许范围内,覆盖 IN 条件
+                wrapper.clear();
+                wrapper.eq(StatementOrder::getCustomerId, customerId);
+                wrapper.eq(StatementOrder::getStatementStatus, statementStatus);
+            }
+            // 如果用户指定了不允许的状态(0或4),保持 IN 条件,会返回空结果
+        }
+
+        // 添加其他查询条件
+        if (bo.getStatementOrderNo() != null && !bo.getStatementOrderNo().isEmpty()) {
+            wrapper.like(StatementOrder::getStatementOrderNo, bo.getStatementOrderNo());
+        }
+        if (bo.getIsInvoiceStatus() != null && !bo.getIsInvoiceStatus().isEmpty()) {
+            wrapper.eq(StatementOrder::getIsInvoiceStatus, bo.getIsInvoiceStatus());
+        }
+        if (bo.getIsPaymentStatus() != null && !bo.getIsPaymentStatus().isEmpty()) {
+            wrapper.eq(StatementOrder::getIsPaymentStatus, bo.getIsPaymentStatus());
+        }
+
+        // 使用新添加的 PC 端专用方法
+        return statementOrderService.queryPageListByWrapper(wrapper, pageQuery);
+    }
+
+    /**
+     * 获取对账单详细信息
+     * PC端用户只能查询自己企业的对账单
+     */
+    @GetMapping("/{id}")
+    public R<StatementOrderVo> getInfo(@NotNull(message = "主键不能为空")
+                                       @PathVariable("id") Long id) {
+        StatementOrderVo vo = statementOrderService.queryById(id);
+        if (vo != null) {
+            Long customerId = LoginHelper.getUserId();
+            if (!customerId.equals(vo.getCustomerId())) {
+                return R.fail("无权访问该对账单");
+            }
+        }
+        return R.ok(vo);
+    }
+
+    /**
+     * 查询当前企业的对账单明细列表
+     */
+    @GetMapping("/details")
+    public TableDataInfo<StatementDetailVo> getDetails(PageQuery pageQuery) {
+        Long customerId = LoginHelper.getUserId();
+        return statementOrderService.listDetailsByCustomerIdPage(customerId, pageQuery);
+    }
+
+    /**
+     * 确认对账单
+     */
+    @Log(title = "PC端-对账单确认", businessType = BusinessType.UPDATE)
+    @PutMapping("/confirm")
+    public R<Void> confirm(@RequestBody StatementOrderBo bo) {
+        Long customerId = LoginHelper.getUserId();
+        StatementOrderVo existingStatement = statementOrderService.queryById(bo.getId());
+        if (existingStatement == null) {
+            return R.fail("对账单不存在");
+        }
+        if (!customerId.equals(existingStatement.getCustomerId())) {
+            return R.fail("无权确认该对账单");
+        }
+        bo.setCustomerId(customerId);
+        bo.setStatementStatus("1");
+        return toAjax(statementOrderService.updateStatus(bo));
+    }
+
+    /**
+     * 驳回对账单
+     */
+    @Log(title = "PC端-对账单驳回", businessType = BusinessType.UPDATE)
+    @PutMapping("/reject")
+    public R<Void> reject(@RequestBody StatementOrderBo bo) {
+        Long customerId = LoginHelper.getUserId();
+        StatementOrderVo existingStatement = statementOrderService.queryById(bo.getId());
+        if (existingStatement == null) {
+            return R.fail("对账单不存在");
+        }
+        if (!customerId.equals(existingStatement.getCustomerId())) {
+            return R.fail("无权驳回该对账单");
+        }
+        bo.setCustomerId(customerId);
+        bo.setStatementStatus("2");
+        return toAjax(statementOrderService.updateStatus(bo));
+    }
+}

+ 103 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/pc/controller/PcStatementProductController.java

@@ -0,0 +1,103 @@
+package org.dromara.bill.pc.controller;
+
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import lombok.RequiredArgsConstructor;
+import jakarta.validation.constraints.*;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.bill.domain.bo.StatementProductBo;
+import org.dromara.bill.domain.dto.StatementOrderItem;
+import org.dromara.bill.domain.vo.StatementProductVo;
+import org.dromara.bill.domain.vo.StatementOrderVo;
+import org.dromara.bill.service.IStatementProductService;
+import org.dromara.bill.service.IStatementOrderService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * PC端 - 对账单商品管理
+ * 前端访问路由地址为:/pc/enterprise/statementProduct
+ *
+ * @author Claude
+ * @date 2026-01-29
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/pc/enterprise/statementProduct")
+public class PcStatementProductController extends BaseController {
+
+    private final IStatementProductService statementProductService;
+    private final IStatementOrderService statementOrderService;
+
+    /**
+     * 查询对账单商品列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo<StatementProductVo> list(StatementProductBo bo, PageQuery pageQuery) {
+        if (bo.getStatementOrderId() != null) {
+            StatementOrderVo statement = statementOrderService.queryById(bo.getStatementOrderId());
+            if (statement != null) {
+                Long customerId = LoginHelper.getUserId();
+                if (!customerId.equals(statement.getCustomerId())) {
+                    throw new IllegalArgumentException("无权访问该对账单商品");
+                }
+            }
+        }
+        return statementProductService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 根据订单ID查询对账单商品列表
+     */
+    @PostMapping("/getStatementProductList")
+    public TableDataInfo<StatementProductVo> getStatementProductList(
+        @RequestBody List<StatementOrderItem> items) {
+
+        if (CollectionUtils.isEmpty(items)) {
+            throw new IllegalArgumentException("至少选择一个订单");
+        }
+
+        if (items.stream().anyMatch(item -> item.getStatementOrderId() == null || item.getOrderId() == null)) {
+            throw new IllegalArgumentException("statementOrderId 和 orderId 不能为空");
+        }
+
+        Long customerId = LoginHelper.getUserId();
+        for (StatementOrderItem item : items) {
+            Long statementOrderId = Long.parseLong(item.getStatementOrderId());
+            StatementOrderVo statement = statementOrderService.queryById(statementOrderId);
+            if (statement == null) {
+                throw new IllegalArgumentException("对账单不存在");
+            }
+            if (!customerId.equals(statement.getCustomerId())) {
+                throw new IllegalArgumentException("无权访问该对账单商品");
+            }
+        }
+
+        return statementOrderService.getStatementProductList(items);
+    }
+
+    /**
+     * 获取对账单商品详细信息
+     */
+    @GetMapping("/{id}")
+    public R<StatementProductVo> getInfo(@NotNull(message = "主键不能为空")
+                                         @PathVariable("id") Long id) {
+        StatementProductVo vo = statementProductService.queryById(id);
+        if (vo != null && vo.getStatementOrderId() != null) {
+            StatementOrderVo statement = statementOrderService.queryById(vo.getStatementOrderId());
+            if (statement != null) {
+                Long customerId = LoginHelper.getUserId();
+                if (!customerId.equals(statement.getCustomerId())) {
+                    return R.fail("无权访问该对账单商品");
+                }
+            }
+        }
+        return R.ok(vo);
+    }
+}

+ 10 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/service/IStatementOrderService.java

@@ -56,6 +56,16 @@ public interface IStatementOrderService extends IService<StatementOrder> {
      */
     TableDataInfo<StatementOrderVo> queryPageList(StatementOrderBo bo, PageQuery pageQuery);
 
+    /**
+     * 使用自定义 Wrapper 分页查询对账单列表
+     * 用于 PC 端权限控制,支持自定义查询条件
+     *
+     * @param wrapper   查询条件包装器
+     * @param pageQuery 分页参数
+     * @return 对账单主分页列表
+     */
+    TableDataInfo<StatementOrderVo> queryPageListByWrapper(com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<StatementOrder> wrapper, PageQuery pageQuery);
+
 
     TableDataInfo<StatementProductVo> getStatementProductList(List<StatementOrderItem> items);
 

+ 14 - 0
ruoyi-modules/ruoyi-bill/src/main/java/org/dromara/bill/service/impl/StatementOrderServiceImpl.java

@@ -191,6 +191,20 @@ public class StatementOrderServiceImpl extends ServiceImpl<StatementOrderMapper,
         return TableDataInfo.build(result);
     }
 
+    /**
+     * 使用自定义 Wrapper 分页查询对账单列表
+     * 用于 PC 端权限控制,支持自定义查询条件
+     *
+     * @param wrapper   查询条件包装器
+     * @param pageQuery 分页参数
+     * @return 对账单主分页列表
+     */
+    @Override
+    public TableDataInfo<StatementOrderVo> queryPageListByWrapper(LambdaQueryWrapper<StatementOrder> wrapper, PageQuery pageQuery) {
+        Page<StatementOrderVo> result = baseMapper.selectVoPage(pageQuery.build(), wrapper);
+        return TableDataInfo.build(result);
+    }
+
     /**
      * 查询符合条件的对账单主列表
      *

+ 118 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/pc/controller/PcServicePersonController.java

@@ -0,0 +1,118 @@
+package org.dromara.customer.pc.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.apache.dubbo.config.annotation.DubboReference;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.customer.domain.vo.CustomerSalesInfoVo;
+import org.dromara.customer.service.ICustomerSalesInfoService;
+import org.dromara.system.api.RemoteComStaffService;
+import org.dromara.system.api.domain.vo.RemoteComStaffVo;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.*;
+
+/**
+ * PC端 - 专属服务人员查询
+ * 前端访问路由地址为:/pc/enterprise/servicePerson
+ *
+ * @author Claude
+ * @date 2026-01-28
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/pc/enterprise/servicePerson")
+public class PcServicePersonController extends BaseController {
+
+    private final ICustomerSalesInfoService customerSalesInfoService;
+
+    @DubboReference
+    private RemoteComStaffService remoteComStaffService;
+
+    /**
+     * 查询当前企业的专属服务人员
+     * 返回:销售人员(专属采购顾问)、服务人员(客服人员)
+     */
+    @GetMapping("/list")
+    public R<List<ServicePersonVO>> getServicePersons() {
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getUserId();
+
+        // 查询客户销售信息
+        CustomerSalesInfoVo salesInfo = customerSalesInfoService.queryByCustomerId(customerId);
+        if (salesInfo == null) {
+            return R.ok(Collections.emptyList());
+        }
+
+        // 收集服务人员ID
+        Set<Long> staffIds = new HashSet<>();
+        if (salesInfo.getSalesPersonId() != null) {
+            staffIds.add(salesInfo.getSalesPersonId());
+        }
+        if (salesInfo.getServiceStaffId() != null) {
+            staffIds.add(salesInfo.getServiceStaffId());
+        }
+
+        if (staffIds.isEmpty()) {
+            return R.ok(Collections.emptyList());
+        }
+
+        // 通过Dubbo远程调用获取人员详细信息
+        List<RemoteComStaffVo> staffList = remoteComStaffService.selectStaffByIds(staffIds);
+        if (staffList == null || staffList.isEmpty()) {
+            return R.ok(Collections.emptyList());
+        }
+
+        // 组装返回数据
+        List<ServicePersonVO> result = new ArrayList<>();
+
+        // 添加销售人员(专属采购顾问)
+        if (salesInfo.getSalesPersonId() != null) {
+            staffList.stream()
+                .filter(staff -> salesInfo.getSalesPersonId().equals(staff.getStaffId()))
+                .findFirst()
+                .ifPresent(staff -> {
+                    ServicePersonVO vo = new ServicePersonVO();
+                    vo.setName(staff.getStaffName());
+                    vo.setType("专属采购顾问");
+                    vo.setDepartment(staff.getDeptName());
+                    vo.setPhone(staff.getPhone());
+                    vo.setAvatar("");
+                    result.add(vo);
+                });
+        }
+
+        // 添加服务人员(客服人员)
+        if (salesInfo.getServiceStaffId() != null) {
+            staffList.stream()
+                .filter(staff -> salesInfo.getServiceStaffId().equals(staff.getStaffId()))
+                .findFirst()
+                .ifPresent(staff -> {
+                    ServicePersonVO vo = new ServicePersonVO();
+                    vo.setName(staff.getStaffName());
+                    vo.setType("客服人员");
+                    vo.setDepartment(staff.getDeptName());
+                    vo.setPhone(staff.getPhone());
+                    vo.setAvatar("");
+                    result.add(vo);
+                });
+        }
+
+        return R.ok(result);
+    }
+
+    /**
+     * 服务人员视图对象
+     */
+    @lombok.Data
+    public static class ServicePersonVO {
+        private String name;
+        private String type;
+        private String department;
+        private String phone;
+        private String avatar;
+    }
+}

+ 171 - 0
ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/pc/controller/PcOrderController.java

@@ -0,0 +1,171 @@
+package org.dromara.order.pc.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.order.domain.bo.OrderMainBo;
+import org.dromara.order.domain.vo.OrderMainVo;
+import org.dromara.order.domain.vo.OrderProductVo;
+import org.dromara.order.domain.vo.OrderStatusStats;
+import org.dromara.order.service.IOrderMainService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.validation.constraints.NotNull;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * PC端 - 订单管理
+ * 前端访问路由地址为:/pc/enterprise/order
+ *
+ * @author Claude
+ * @date 2026-01-28
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/pc/enterprise/order")
+public class PcOrderController extends BaseController {
+
+    private final IOrderMainService orderMainService;
+
+    /**
+     * 查询当前企业的订单列表
+     * PC端用户只能查询自己企业的订单
+     */
+    @GetMapping("/list")
+    public TableDataInfo<OrderMainVo> list(OrderMainBo bo, PageQuery pageQuery) {
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getUserId();
+        // 强制设置企业ID,防止越权访问
+        bo.setCustomerId(customerId);
+
+        return orderMainService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 查询当前企业的订单状态统计
+     * 返回各状态订单数量
+     */
+    @GetMapping("/statusStats")
+    public R<OrderStatusStats> getStatusStats() {
+        return R.ok(orderMainService.queryOrderStatusStats());
+    }
+
+    /**
+     * 获取订单详细信息
+     * PC端用户只能查询自己企业的订单
+     *
+     * @param id 订单主键
+     */
+    @GetMapping("/{id}")
+    public R<OrderMainVo> getInfo(@NotNull(message = "主键不能为空")
+                                  @PathVariable("id") Long id) {
+        // 查询订单信息
+        OrderMainVo vo = orderMainService.queryById(id);
+
+        // 验证订单是否属于当前用户的企业
+        if (vo != null) {
+            Long customerId = LoginHelper.getUserId();
+            if (!customerId.equals(vo.getCustomerId())) {
+                return R.fail("无权访问该订单");
+            }
+        }
+
+        return R.ok(vo);
+    }
+
+    /**
+     * 根据订单ID查询订单商品明细
+     * PC端用户只能查询自己企业的订单商品
+     *
+     * @param orderIds 订单ID列表
+     */
+    @GetMapping("/products")
+    public TableDataInfo<OrderProductVo> getOrderProducts(@RequestParam("orderIds") List<Long> orderIds) {
+        if (orderIds == null || orderIds.isEmpty()) {
+            throw new IllegalArgumentException("订单ID列表不能为空");
+        }
+        if (orderIds.size() > 1000) {
+            throw new IllegalArgumentException("订单ID数量不能超过1000个");
+        }
+
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getUserId();
+
+        // 验证所有订单是否都属于当前用户的企业
+        for (Long orderId : orderIds) {
+            OrderMainVo order = orderMainService.queryById(orderId);
+            if (order == null) {
+                throw new IllegalArgumentException("订单ID " + orderId + " 不存在");
+            }
+            if (!customerId.equals(order.getCustomerId())) {
+                throw new IllegalArgumentException("无权访问订单ID " + orderId);
+            }
+        }
+
+        Set<Long> uniqueOrderIds = new HashSet<>(orderIds);
+        return orderMainService.getCustomerOrderProductList(uniqueOrderIds);
+    }
+
+    /**
+     * 取消订单
+     * PC端用户只能取消自己企业的订单
+     *
+     * @param bo 订单业务对象
+     */
+    @Log(title = "PC端-订单管理", businessType = BusinessType.UPDATE)
+    @PutMapping("/cancel")
+    public R<Void> cancelOrder(@RequestBody OrderMainBo bo) {
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getUserId();
+
+        // 验证订单是否属于当前用户的企业
+        OrderMainVo existingOrder = orderMainService.queryById(bo.getId());
+        if (existingOrder == null) {
+            return R.fail("订单不存在");
+        }
+        if (!customerId.equals(existingOrder.getCustomerId())) {
+            return R.fail("无权取消该订单");
+        }
+
+        // 强制设置企业ID
+        bo.setCustomerId(customerId);
+
+        return toAjax(orderMainService.updateStatus(bo));
+    }
+
+    /**
+     * 审核订单
+     * PC端企业客户审核自己企业的订单
+     *
+     * @param bo 订单业务对象
+     */
+    @Log(title = "PC端-订单审核", businessType = BusinessType.UPDATE)
+    @PutMapping("/checkStatus")
+    public R<Void> checkStatus(@RequestBody OrderMainBo bo) {
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getUserId();
+
+        // 验证订单是否属于当前用户的企业
+        OrderMainVo existingOrder = orderMainService.queryById(bo.getId());
+        if (existingOrder == null) {
+            return R.fail("订单不存在");
+        }
+        if (!customerId.equals(existingOrder.getCustomerId())) {
+            return R.fail("无权审核该订单");
+        }
+
+        // 强制设置企业ID
+        bo.setCustomerId(customerId);
+
+        return toAjax(orderMainService.updateCheckStatus(bo));
+    }
+}

+ 144 - 0
ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/pc/controller/PcOrderReturnController.java

@@ -0,0 +1,144 @@
+package org.dromara.order.pc.controller;
+
+import lombok.RequiredArgsConstructor;
+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.idempotent.annotation.RepeatSubmit;
+import org.dromara.common.log.annotation.Log;
+import org.dromara.common.log.enums.BusinessType;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.order.domain.bo.OrderReturnBo;
+import org.dromara.order.domain.vo.OrderReturnVo;
+import org.dromara.order.service.IOrderReturnService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import java.util.List;
+
+/**
+ * PC端 - 售后服务管理
+ * 前端访问路由地址为:/pc/enterprise/orderReturn
+ *
+ * @author Claude
+ * @date 2026-01-29
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/pc/enterprise/orderReturn")
+public class PcOrderReturnController extends BaseController {
+
+    private final IOrderReturnService orderReturnService;
+
+    /**
+     * 查询当前企业的售后申请列表
+     * PC端用户只能查询自己企业的售后记录
+     */
+    @GetMapping("/list")
+    public TableDataInfo<OrderReturnVo> list(OrderReturnBo bo, PageQuery pageQuery) {
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getUserId();
+        // 强制设置企业ID,防止越权访问
+        bo.setCustomerId(customerId);
+
+        return orderReturnService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取售后申请详细信息
+     * PC端用户只能查询自己企业的售后记录
+     *
+     * @param id 主键
+     */
+    @GetMapping("/{id}")
+    public R<OrderReturnVo> getInfo(@NotNull(message = "主键不能为空")
+                                    @PathVariable("id") Long id) {
+        // 查询售后信息
+        OrderReturnVo vo = orderReturnService.queryById(id);
+
+        // 验证售后记录是否属于当前用户的企业
+        if (vo != null) {
+            Long customerId = LoginHelper.getUserId();
+            if (!customerId.equals(vo.getCustomerId())) {
+                return R.fail("无权访问该售后记录");
+            }
+        }
+
+        return R.ok(vo);
+    }
+
+    /**
+     * 新增售后申请
+     * 企业客户申请退货/售后服务
+     */
+    @Log(title = "PC端-售后服务", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody OrderReturnBo bo) {
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getUserId();
+        // 强制设置企业ID,防止为其他企业创建售后申请
+        bo.setCustomerId(customerId);
+
+        return toAjax(orderReturnService.insertByBo(bo));
+    }
+
+    /**
+     * 修改售后申请
+     * 企业客户修改自己的售后申请(仅限未审核状态)
+     */
+    @Log(title = "PC端-售后服务", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody OrderReturnBo bo) {
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getUserId();
+
+        // 验证售后记录是否属于当前用户的企业
+        OrderReturnVo existingReturn = orderReturnService.queryById(bo.getId());
+        if (existingReturn == null) {
+            return R.fail("售后记录不存在");
+        }
+        if (!customerId.equals(existingReturn.getCustomerId())) {
+            return R.fail("无权修改该售后记录");
+        }
+
+        // 强制设置企业ID
+        bo.setCustomerId(customerId);
+
+        return toAjax(orderReturnService.updateByBo(bo));
+    }
+
+    /**
+     * 删除售后申请
+     * 企业客户删除自己的售后申请(仅限未审核状态)
+     *
+     * @param ids 主键串
+     */
+    @Log(title = "PC端-售后服务", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        // 获取当前登录用户的企业ID
+        Long customerId = LoginHelper.getUserId();
+
+        // 验证所有售后记录是否都属于当前用户的企业
+        for (Long id : ids) {
+            OrderReturnVo orderReturn = orderReturnService.queryById(id);
+            if (orderReturn == null) {
+                return R.fail("售后记录ID " + id + " 不存在");
+            }
+            if (!customerId.equals(orderReturn.getCustomerId())) {
+                return R.fail("无权删除售后记录ID " + id);
+            }
+        }
+
+        return toAjax(orderReturnService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 10 - 2
ruoyi-modules/ruoyi-order/src/main/java/org/dromara/order/service/impl/OrderMainServiceImpl.java

@@ -214,12 +214,20 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
         lqw.eq(StringUtils.isNotBlank(bo.getOrderCategory()), OrderMain::getOrderCategory, bo.getOrderCategory());
         lqw.eq(StringUtils.isNotBlank(bo.getExpenseType()), OrderMain::getExpenseType, bo.getExpenseType());
         lqw.eq(StringUtils.isNotBlank(bo.getStatus()), OrderMain::getStatus, bo.getStatus());
+        // 关键词搜索:精确匹配订单号
+        if (StringUtils.isNotBlank(bo.getSearchValue())) {
+            lqw.eq(OrderMain::getOrderNo, bo.getSearchValue().trim());
+        }
         if (params != null) {
             lqw.between(params.get("beginTime") != null && params.get("endTime") != null,
                 OrderMain::getOrderTime, params.get("beginTime"), params.get("endTime"));
         }
-        if (null != bo.getOrderStatuses() && bo.getOrderStatuses().contains(",")) {
-            lqw.in(OrderMain::getOrderStatus, bo.getOrderStatuses().split(","));
+        if (StringUtils.isNotBlank(bo.getOrderStatuses())) {
+            if (bo.getOrderStatuses().contains(",")) {
+                lqw.in(OrderMain::getOrderStatus, bo.getOrderStatuses().split(","));
+            } else {
+                lqw.eq(OrderMain::getOrderStatus, bo.getOrderStatuses());
+            }
         }
         return lqw;
     }

+ 104 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/controller/ServiceCaseController.java

@@ -0,0 +1,104 @@
+package org.dromara.product.controller;
+
+import java.util.List;
+
+import lombok.RequiredArgsConstructor;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.constraints.*;
+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.ServiceCaseVo;
+import org.dromara.product.domain.bo.ServiceCaseBo;
+import org.dromara.product.service.IServiceCaseService;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+
+/**
+ * 服务案例
+ * 前端访问路由地址为:/product/serviceCase
+ *
+ * @author LionLi
+ * @date 2026-01-30
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/serviceCase")
+public class ServiceCaseController extends BaseController {
+
+    private final IServiceCaseService serviceCaseService;
+
+    /**
+     * 查询服务案例列表
+     */
+    //@SaCheckPermission("product:serviceCase:list")
+    @GetMapping("/list")
+    public TableDataInfo<ServiceCaseVo> list(ServiceCaseBo bo, PageQuery pageQuery) {
+        return serviceCaseService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 导出服务案例列表
+     */
+    //@SaCheckPermission("product:serviceCase:export")
+    @Log(title = "服务案例", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(ServiceCaseBo bo, HttpServletResponse response) {
+        List<ServiceCaseVo> list = serviceCaseService.queryList(bo);
+        ExcelUtil.exportExcel(list, "服务案例", ServiceCaseVo.class, response);
+    }
+
+    /**
+     * 获取服务案例详细信息
+     *
+     * @param id 主键
+     */
+    //@SaCheckPermission("product:serviceCase:query")
+    @GetMapping("/{id}")
+    public R<ServiceCaseVo> getInfo(@PathVariable("id") Long id) {
+        return R.ok(serviceCaseService.queryById(id));
+    }
+
+    /**
+     * 新增服务案例
+     */
+    //@SaCheckPermission("product:serviceCase:add")
+    @Log(title = "服务案例", businessType = BusinessType.INSERT)
+    @RepeatSubmit()
+    @PostMapping()
+    public R<Void> add(@Validated(AddGroup.class) @RequestBody ServiceCaseBo bo) {
+        return toAjax(serviceCaseService.insertByBo(bo));
+    }
+
+    /**
+     * 修改服务案例
+     */
+    //@SaCheckPermission("product:serviceCase:edit")
+    @Log(title = "服务案例", businessType = BusinessType.UPDATE)
+    @RepeatSubmit()
+    @PutMapping()
+    public R<Void> edit(@Validated(EditGroup.class) @RequestBody ServiceCaseBo bo) {
+        return toAjax(serviceCaseService.updateByBo(bo));
+    }
+
+    /**
+     * 删除服务案例
+     *
+     * @param ids 主键串
+     */
+    //@SaCheckPermission("product:serviceCase:remove")
+    @Log(title = "服务案例", businessType = BusinessType.DELETE)
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@NotEmpty(message = "主键不能为空")
+                          @PathVariable("ids") Long[] ids) {
+        return toAjax(serviceCaseService.deleteWithValidByIds(List.of(ids), true));
+    }
+}

+ 20 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ProductRecommendLink.java

@@ -37,6 +37,26 @@ public class ProductRecommendLink extends TenantEntity {
      */
     private Long productId;
 
+    /**
+     * 方案编号
+     */
+    private Long programId;
+
+    /**
+     * 服务案例编号
+     */
+    private Long serviceCaseId;
+
+    /**
+     * 品牌编号
+     */
+    private Long brandId;
+
+    /**
+     * 排序值
+     */
+    private Integer sort;
+
     /**
      * 删除标志(0代表存在 2代表删除)
      */

+ 106 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/ServiceCase.java

@@ -0,0 +1,106 @@
+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;
+
+/**
+ * 服务案例对象 service_case
+ *
+ * @author LionLi
+ * @date 2026-01-30
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("service_case")
+public class ServiceCase extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "id")
+    private Long id;
+
+    /**
+     * 案例编号
+     */
+    private String serviceCaseNo;
+
+    /**
+     * 案例标题
+     */
+    private String caseTitle;
+
+    /**
+     * 项目简介
+     */
+    private String projectBrief;
+
+    /**
+     * 客户行业
+     */
+    private String clientIndustry;
+
+    /**
+     * 项目类型
+     */
+    private String projectType;
+
+    /**
+     * 上传方案
+     */
+    private String uploadProgram;
+
+    /**
+     * 案例图片URL
+     */
+    private String caseImage;
+
+    /**
+     * 项目详情
+     */
+    private String projectDetails;
+
+    /**
+     * 是否显示(0=不显示 1=显示)
+     */
+    private String isShow;
+
+    /**
+     * 是否推荐(0=不推荐 1=推荐)
+     */
+    private String isRecommend;
+
+    /**
+     * 点赞数
+     */
+    private Integer praise;
+
+    /**
+     * 是否相关(0=不相关 1=相关)
+     */
+    private String isRelevant;
+
+    /**
+     * 平台标识
+     */
+    private String platformCode;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 20 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ProductRecommendLinkBo.java

@@ -37,6 +37,26 @@ public class ProductRecommendLinkBo extends BaseEntity {
     //@NotNull(message = "产品编号不能为空", groups = { AddGroup.class, EditGroup.class })
     private Long productId;
 
+    /**
+     * 方案编号
+     */
+    private Long programId;
+
+    /**
+     * 服务案例编号
+     */
+    private Long serviceCaseId;
+
+    /**
+     * 品牌编号
+     */
+    private Long brandId;
+
+    /**
+     * 排序值
+     */
+    private Integer sort;
+
     /**
      * 备注
      */

+ 98 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/bo/ServiceCaseBo.java

@@ -0,0 +1,98 @@
+package org.dromara.product.domain.bo;
+
+import org.dromara.product.domain.ServiceCase;
+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.*;
+
+/**
+ * 服务案例业务对象 service_case
+ *
+ * @author LionLi
+ * @date 2026-01-30
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = ServiceCase.class, reverseConvertGenerate = false)
+public class ServiceCaseBo extends BaseEntity {
+
+    /**
+     * 主键ID
+     */
+    private Long id;
+
+    /**
+     * 案例编号
+     */
+    private String serviceCaseNo;
+
+    /**
+     * 案例标题
+     */
+    private String caseTitle;
+
+    /**
+     * 项目简介
+     */
+    private String projectBrief;
+
+    /**
+     * 客户行业
+     */
+    private String clientIndustry;
+
+    /**
+     * 项目类型
+     */
+    private String projectType;
+
+    /**
+     * 上传方案
+     */
+    private String uploadProgram;
+
+    /**
+     * 案例图片URL
+     */
+    private String caseImage;
+
+    /**
+     * 项目详情
+     */
+    private String projectDetails;
+
+    /**
+     * 是否显示(0=不显示 1=显示)
+     */
+    private String isShow;
+
+    /**
+     * 是否推荐(0=不推荐 1=推荐)
+     */
+    private String isRecommend;
+
+    /**
+     * 点赞数
+     */
+    private Integer praise;
+
+    /**
+     * 是否相关(0=不相关 1=相关)
+     */
+    private String isRelevant;
+
+    /**
+     * 平台标识
+     */
+    private String platformCode;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 24 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ProductRecommendLinkVo.java

@@ -46,6 +46,30 @@ public class ProductRecommendLinkVo implements Serializable {
     @ExcelProperty(value = "产品编号")
     private Long productId;
 
+    /**
+     * 方案编号
+     */
+    @ExcelProperty(value = "方案编号")
+    private Long programId;
+
+    /**
+     * 服务案例编号
+     */
+    @ExcelProperty(value = "服务案例编号")
+    private Long serviceCaseId;
+
+    /**
+     * 品牌编号
+     */
+    @ExcelProperty(value = "品牌编号")
+    private Long brandId;
+
+    /**
+     * 排序值
+     */
+    @ExcelProperty(value = "排序值")
+    private Integer sort;
+
     /**
      * 备注
      */

+ 104 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/domain/vo/ServiceCaseVo.java

@@ -0,0 +1,104 @@
+package org.dromara.product.domain.vo;
+
+import org.dromara.product.domain.ServiceCase;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 服务案例视图对象 service_case
+ *
+ * @author LionLi
+ * @date 2026-01-30
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = ServiceCase.class)
+public class ServiceCaseVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    private Long id;
+
+    /**
+     * 案例编号
+     */
+    @ExcelProperty(value = "案例编号")
+    private String serviceCaseNo;
+
+    /**
+     * 案例标题
+     */
+    @ExcelProperty(value = "案例标题")
+    private String caseTitle;
+
+    /**
+     * 项目简介
+     */
+    @ExcelProperty(value = "项目简介")
+    private String projectBrief;
+
+    /**
+     * 客户行业
+     */
+    @ExcelProperty(value = "客户行业")
+    private String clientIndustry;
+
+    /**
+     * 项目类型
+     */
+    @ExcelProperty(value = "项目类型")
+    private String projectType;
+
+    /**
+     * 上传方案
+     */
+    @ExcelProperty(value = "上传方案")
+    private String uploadProgram;
+
+    /**
+     * 案例图片URL
+     */
+    @ExcelProperty(value = "案例图片")
+    private String caseImage;
+
+    /**
+     * 是否显示
+     */
+    @ExcelProperty(value = "是否显示")
+    private String isShow;
+
+    /**
+     * 是否推荐
+     */
+    @ExcelProperty(value = "是否推荐")
+    private String isRecommend;
+
+    /**
+     * 点赞数
+     */
+    @ExcelProperty(value = "点赞数")
+    private Integer praise;
+
+    /**
+     * 平台标识
+     */
+    @ExcelProperty(value = "平台标识")
+    private String platformCode;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+}

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

@@ -0,0 +1,15 @@
+package org.dromara.product.mapper;
+
+import org.dromara.product.domain.ServiceCase;
+import org.dromara.product.domain.vo.ServiceCaseVo;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+
+/**
+ * 服务案例Mapper接口
+ *
+ * @author LionLi
+ * @date 2026-01-30
+ */
+public interface ServiceCaseMapper extends BaseMapperPlus<ServiceCase, ServiceCaseVo> {
+
+}

+ 70 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/IServiceCaseService.java

@@ -0,0 +1,70 @@
+package org.dromara.product.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.dromara.product.domain.ServiceCase;
+import org.dromara.product.domain.vo.ServiceCaseVo;
+import org.dromara.product.domain.bo.ServiceCaseBo;
+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-30
+ */
+public interface IServiceCaseService extends IService<ServiceCase>{
+
+    /**
+     * 查询服务案例
+     *
+     * @param id 主键
+     * @return 服务案例
+     */
+    ServiceCaseVo queryById(Long id);
+
+    /**
+     * 分页查询服务案例列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 服务案例分页列表
+     */
+    TableDataInfo<ServiceCaseVo> queryPageList(ServiceCaseBo bo, PageQuery pageQuery);
+
+    /**
+     * 查询符合条件的服务案例列表
+     *
+     * @param bo 查询条件
+     * @return 服务案例列表
+     */
+    List<ServiceCaseVo> queryList(ServiceCaseBo bo);
+
+    /**
+     * 新增服务案例
+     *
+     * @param bo 服务案例
+     * @return 是否新增成功
+     */
+    Boolean insertByBo(ServiceCaseBo bo);
+
+    /**
+     * 修改服务案例
+     *
+     * @param bo 服务案例
+     * @return 是否修改成功
+     */
+    Boolean updateByBo(ServiceCaseBo bo);
+
+    /**
+     * 校验并批量删除服务案例信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+}

+ 137 - 0
ruoyi-modules/ruoyi-product/src/main/java/org/dromara/product/service/impl/ServiceCaseServiceImpl.java

@@ -0,0 +1,137 @@
+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.ServiceCaseBo;
+import org.dromara.product.domain.vo.ServiceCaseVo;
+import org.dromara.product.domain.ServiceCase;
+import org.dromara.product.mapper.ServiceCaseMapper;
+import org.dromara.product.service.IServiceCaseService;
+
+import java.util.List;
+import java.util.Collection;
+
+/**
+ * 服务案例Service业务层处理
+ *
+ * @author LionLi
+ * @date 2026-01-30
+ */
+@Slf4j
+@RequiredArgsConstructor
+@Service
+public class ServiceCaseServiceImpl extends ServiceImpl<ServiceCaseMapper, ServiceCase> implements IServiceCaseService {
+
+    private final ServiceCaseMapper baseMapper;
+
+    /**
+     * 查询服务案例
+     *
+     * @param id 主键
+     * @return 服务案例
+     */
+    @Override
+    public ServiceCaseVo queryById(Long id){
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询服务案例列表
+     *
+     * @param bo        查询条件
+     * @param pageQuery 分页参数
+     * @return 服务案例分页列表
+     */
+    @Override
+    public TableDataInfo<ServiceCaseVo> queryPageList(ServiceCaseBo bo, PageQuery pageQuery) {
+        LambdaQueryWrapper<ServiceCase> lqw = buildQueryWrapper(bo);
+        Page<ServiceCaseVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 查询符合条件的服务案例列表
+     *
+     * @param bo 查询条件
+     * @return 服务案例列表
+     */
+    @Override
+    public List<ServiceCaseVo> queryList(ServiceCaseBo bo) {
+        LambdaQueryWrapper<ServiceCase> lqw = buildQueryWrapper(bo);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    private LambdaQueryWrapper<ServiceCase> buildQueryWrapper(ServiceCaseBo bo) {
+        LambdaQueryWrapper<ServiceCase> lqw = Wrappers.lambdaQuery();
+        lqw.orderByDesc(ServiceCase::getId);
+        lqw.like(StringUtils.isNotBlank(bo.getServiceCaseNo()), ServiceCase::getServiceCaseNo, bo.getServiceCaseNo());
+        lqw.like(StringUtils.isNotBlank(bo.getCaseTitle()), ServiceCase::getCaseTitle, bo.getCaseTitle());
+        lqw.eq(StringUtils.isNotBlank(bo.getClientIndustry()), ServiceCase::getClientIndustry, bo.getClientIndustry());
+        lqw.eq(StringUtils.isNotBlank(bo.getProjectType()), ServiceCase::getProjectType, bo.getProjectType());
+        lqw.eq(StringUtils.isNotBlank(bo.getIsShow()), ServiceCase::getIsShow, bo.getIsShow());
+        lqw.eq(StringUtils.isNotBlank(bo.getIsRecommend()), ServiceCase::getIsRecommend, bo.getIsRecommend());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), ServiceCase::getPlatformCode, bo.getPlatformCode());
+        return lqw;
+    }
+
+    /**
+     * 新增服务案例
+     *
+     * @param bo 服务案例
+     * @return 是否新增成功
+     */
+    @Override
+    public Boolean insertByBo(ServiceCaseBo bo) {
+        ServiceCase add = MapstructUtils.convert(bo, ServiceCase.class);
+        validEntityBeforeSave(add);
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            bo.setId(add.getId());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改服务案例
+     *
+     * @param bo 服务案例
+     * @return 是否修改成功
+     */
+    @Override
+    public Boolean updateByBo(ServiceCaseBo bo) {
+        ServiceCase update = MapstructUtils.convert(bo, ServiceCase.class);
+        validEntityBeforeSave(update);
+        return baseMapper.updateById(update) > 0;
+    }
+
+    /**
+     * 保存前的数据校验
+     */
+    private void validEntityBeforeSave(ServiceCase entity){
+        // 可以在此添加业务校验逻辑
+    }
+
+    /**
+     * 校验并批量删除服务案例信息
+     *
+     * @param ids     待删除的主键集合
+     * @param isValid 是否进行有效性校验
+     * @return 是否删除成功
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        if(isValid){
+            // 可以在此添加删除前的校验逻辑
+        }
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+}

+ 6 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/domain/vo/ComStaffVo.java

@@ -9,7 +9,9 @@ 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 io.github.linpeilie.annotations.AutoMappers;
 import lombok.Data;
+import org.dromara.system.api.domain.vo.RemoteComStaffVo;
 
 import java.io.Serial;
 import java.io.Serializable;
@@ -24,7 +26,10 @@ import java.util.Date;
  */
 @Data
 @ExcelIgnoreUnannotated
-@AutoMapper(target = ComStaff.class)
+@AutoMappers({
+    @AutoMapper(target = ComStaff.class),
+    @AutoMapper(target = RemoteComStaffVo.class)
+})
 public class ComStaffVo implements Serializable {
 
     @Serial

+ 9 - 1
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/dubbo/RemoteComStaffServiceImpl.java

@@ -2,8 +2,10 @@ package org.dromara.system.dubbo;
 
 import lombok.RequiredArgsConstructor;
 import org.apache.dubbo.config.annotation.DubboService;
+import org.dromara.common.core.utils.MapstructUtils;
 import org.dromara.system.api.RemoteComStaffService;
-import org.dromara.system.domain.ComStaff;
+import org.dromara.system.api.domain.vo.RemoteComStaffVo;
+import org.dromara.system.domain.vo.ComStaffVo;
 import org.dromara.system.service.IComStaffService;
 import org.springframework.stereotype.Service;
 
@@ -27,4 +29,10 @@ public class RemoteComStaffServiceImpl implements RemoteComStaffService {
     public Map<String, String> selectStaffNameAndCode() {
         return comStaffService.selectStaffNameAndCode();
     }
+
+    @Override
+    public List<RemoteComStaffVo> selectStaffByIds(Set<Long> ids) {
+        List<ComStaffVo> staffList = comStaffService.queryByIds(ids);
+        return MapstructUtils.convert(staffList, RemoteComStaffVo.class);
+    }
 }

+ 8 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/IComStaffService.java

@@ -46,6 +46,14 @@ public interface IComStaffService extends IService<ComStaff> {
      */
     List<ComStaffVo> queryList(ComStaffBo bo);
 
+    /**
+     * 根据人员ID批量查询人员信息
+     *
+     * @param ids 人员ID集合
+     * @return 人员信息列表
+     */
+    List<ComStaffVo> queryByIds(Set<Long> ids);
+
     /**
      * 新增人员信息
      *

+ 44 - 0
ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/ComStaffServiceImpl.java

@@ -90,6 +90,50 @@ public class ComStaffServiceImpl extends ServiceImpl<ComStaffMapper, ComStaff> i
         return baseMapper.selectVoList(lqw);
     }
 
+    /**
+     * 根据人员ID批量查询人员信息
+     *
+     * @param ids 人员ID集合
+     * @return 人员信息列表
+     */
+    @Override
+    public List<ComStaffVo> queryByIds(Set<Long> ids) {
+        if (ids == null || ids.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        LambdaQueryWrapper<ComStaff> lqw = Wrappers.lambdaQuery();
+        lqw.in(ComStaff::getStaffId, ids);
+        List<ComStaffVo> records = baseMapper.selectVoList(lqw);
+
+        if (CollUtil.isNotEmpty(records)) {
+            Set<Long> deptIds = records.stream().map(ComStaffVo::getDeptId).filter(Objects::nonNull).collect(Collectors.toSet());
+            Map<Long, String> deptMap = new HashMap<>();
+            if (CollUtil.isNotEmpty(deptIds)) {
+                deptMap = sysDeptMapper.selectList(new LambdaQueryWrapper<SysDept>()
+                        .select(SysDept::getDeptId, SysDept::getDeptName).in(SysDept::getDeptId, deptIds)).stream()
+                    .collect(Collectors.toMap(SysDept::getDeptId, SysDept::getDeptName));
+            }
+
+            Set<Long> postIds = records.stream().map(ComStaffVo::getPostId).filter(Objects::nonNull).collect(Collectors.toSet());
+            Map<Long, String> postMap = new HashMap<>();
+            if (CollUtil.isNotEmpty(postIds)) {
+                postMap = sysPostMapper.selectList(new LambdaQueryWrapper<SysPost>()
+                        .select(SysPost::getPostId, SysPost::getPostName).in(SysPost::getPostId, postIds)).stream()
+                    .collect(Collectors.toMap(SysPost::getPostId, SysPost::getPostName));
+            }
+
+            Map<Long, String> finalDeptMap = deptMap;
+            Map<Long, String> finalPostMap = postMap;
+            records.forEach(item -> {
+                item.setDeptName(finalDeptMap.get(item.getDeptId()));
+                item.setPostName(finalPostMap.get(item.getPostId()));
+            });
+        }
+
+        return records;
+    }
+
     private LambdaQueryWrapper<ComStaff> buildQueryWrapper(ComStaffBo bo) {
         Map<String, Object> params = bo.getParams();
         LambdaQueryWrapper<ComStaff> lqw = Wrappers.lambdaQuery();

+ 5 - 5
ruoyi-modules/ruoyi-system/src/main/resources/application.yml

@@ -13,6 +13,11 @@ spring:
 # 👇 新增:允许 Bean 定义覆盖(解决 Filter/Bean 冲突)
   main:
     allow-bean-definition-overriding: true
+  config:
+    import:
+      - optional:nacos:application-common.yml
+      - optional:nacos:datasource.yml
+      - optional:nacos:${spring.application.name}.yml
 --- # nacos 配置
 spring:
   cloud:
@@ -29,8 +34,3 @@ spring:
         # 配置组
         group: @nacos.config.group@
         namespace: ${spring.profiles.active}
-  config:
-    import:
-      - optional:nacos:application-common.yml
-      - optional:nacos:datasource.yml
-      - optional:nacos:${spring.application.name}.yml