瀏覽代碼

销售线索与项目商机代码

沐梦. 1 月之前
父節點
當前提交
1255847502
共有 37 個文件被更改,包括 2590 次插入0 次删除
  1. 18 0
      pom.xml
  2. 35 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CrmStaffController.java
  3. 43 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerDictController.java
  4. 32 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/OperationLogController.java
  5. 84 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/SalesleadsController.java
  6. 66 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/TeamMemberController.java
  7. 58 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/constant/CustomerConstants.java
  8. 106 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/CrmStaff.java
  9. 67 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/CustomerDict.java
  10. 91 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/OperationLog.java
  11. 205 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/Salesleads.java
  12. 72 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/TeamMember.java
  13. 72 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/OperationLogBo.java
  14. 132 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/SalesleadsBo.java
  15. 66 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/TeamMemberBo.java
  16. 42 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CrmStaffVo.java
  17. 63 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerDictVo.java
  18. 87 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/OperationLogVo.java
  19. 160 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/SalesleadsVo.java
  20. 76 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/TeamMemberVo.java
  21. 15 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CrmStaffMapper.java
  22. 13 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerDictMapper.java
  23. 15 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/OperationLogMapper.java
  24. 31 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/SalesleadsMapper.java
  25. 48 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/TeamMemberMapper.java
  26. 30 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICrmStaffService.java
  27. 22 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerDictService.java
  28. 41 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/IOperationLogService.java
  29. 65 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ISalesleadsService.java
  30. 46 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ITeamMemberService.java
  31. 59 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CrmStaffServiceImpl.java
  32. 80 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerDictServiceImpl.java
  33. 84 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/OperationLogServiceImpl.java
  34. 191 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/SalesleadsServiceImpl.java
  35. 158 0
      ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/TeamMemberServiceImpl.java
  36. 46 0
      ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/SalesleadsMapper.xml
  37. 71 0
      ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/TeamMemberMapper.xml

+ 18 - 0
pom.xml

@@ -93,8 +93,26 @@
                 <nacos.password>nacos</nacos.password>
                 <logstash.address>127.0.0.1:4560</logstash.address>
             </properties>
+        </profile>
 
+        <profile>
+            <id>tys</id>
+            <properties>
+                <!-- 环境标识,需要与配置文件的名称相对应 -->
+                <profiles.active>7e606cd1-c8f7-4449-bbca-493e4f3d1d64</profiles.active>
+                <nacos.server>127.0.0.1:8848</nacos.server>
+                <nacos.discovery.group>DEFAULT_GROUP</nacos.discovery.group>
+                <nacos.config.group>DEFAULT_GROUP</nacos.config.group>
+                <nacos.username>nacos</nacos.username>
+                <nacos.password>nacos</nacos.password>
+                <logstash.address>127.0.0.1:4560</logstash.address>
+            </properties>
+            <activation>
+                <!-- 默认环境 -->
+                <activeByDefault>true</activeByDefault>
+            </activation>
         </profile>
+
         <profile>
             <id>zl</id>
             <properties>

+ 35 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CrmStaffController.java

@@ -0,0 +1,35 @@
+package org.dromara.customer.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.customer.domain.vo.CrmStaffVo;
+import org.dromara.customer.service.ICrmStaffService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * CRM人员信息
+ * 前端访问路由地址为:/customer/crmStaff
+ * @author tys
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/crmStaff")
+public class CrmStaffController extends BaseController {
+
+    private final ICrmStaffService crmStaffService;
+
+    /**
+     * 查询CRM人员下拉选项列表(供前端下拉框使用)
+     */
+    @GetMapping("/options")
+    public R<List<CrmStaffVo>> selectOptionList() {
+        return R.ok(crmStaffService.selectOptionList());
+    }
+}

+ 43 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/CustomerDictController.java

@@ -0,0 +1,43 @@
+package org.dromara.customer.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.customer.domain.vo.CustomerDictVo;
+import org.dromara.customer.service.ICustomerDictService;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 客户字典控制器
+ */
+@Validated
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/dict")
+public class CustomerDictController extends BaseController {
+
+    private final ICustomerDictService customerDictService;
+
+    /**
+     * 根据字典类型获取数据列表
+     */
+    @GetMapping("/type/{dictType}")
+    public R<List<CustomerDictVo>> getDictByType(@PathVariable String dictType) {
+        return R.ok(customerDictService.selectListByType(dictType));
+    }
+
+    /**
+     * 根据字典code获取通用字典数据列表
+     */
+    @GetMapping("/common/{dictCode}")
+    public R<List<CustomerDictVo>> getCommonDictByCode(@PathVariable String dictCode) {
+        return R.ok(customerDictService.selectListByCode(dictCode));
+    }
+
+}

+ 32 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/OperationLogController.java

@@ -0,0 +1,32 @@
+package org.dromara.customer.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.customer.domain.bo.OperationLogBo;
+import org.dromara.customer.domain.vo.OperationLogVo;
+import org.dromara.customer.service.IOperationLogService;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 操作日志Controller
+ * @author tys
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/crm/operationLog")
+public class OperationLogController {
+
+    private final IOperationLogService operationLogService;
+
+    /**
+     * 查询操作日志列表
+     */
+    @GetMapping("/list")
+    public R<List<OperationLogVo>> list(OperationLogBo bo) {
+        return R.ok(operationLogService.queryList(bo));
+    }
+}

+ 84 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/SalesleadsController.java

@@ -0,0 +1,84 @@
+package org.dromara.customer.controller;
+
+import lombok.RequiredArgsConstructor;
+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.web.core.BaseController;
+import org.dromara.customer.domain.bo.SalesleadsBo;
+import org.dromara.customer.domain.vo.SalesleadsVo;
+import org.dromara.customer.service.ISalesleadsService;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+
+/**
+ * 销售线索/项目商机 前端控制器
+ *
+ * @author tys
+ * @date 2026-04-16
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/salesleads")
+public class SalesleadsController extends BaseController {
+
+    private final ISalesleadsService salesleadsService;
+
+    /**
+     * 查询列表
+     */
+    @GetMapping("/list")
+    public TableDataInfo<SalesleadsVo> list(SalesleadsBo bo, PageQuery pageQuery) {
+        return salesleadsService.queryPageList(bo, pageQuery);
+    }
+
+    /**
+     * 获取详细信息
+     */
+    @GetMapping("/{id}")
+    public R<SalesleadsVo> getInfo(@PathVariable Long id) {
+        return R.ok(salesleadsService.queryById(id));
+    }
+
+    /**
+     * 新增
+     */
+    @PostMapping
+    public R<Void> add(@RequestBody SalesleadsBo bo) {
+        return toAjax(salesleadsService.insertLeads(bo));
+    }
+
+    /**
+     * 修改
+     */
+    @PutMapping
+    public R<Void> edit(@RequestBody SalesleadsBo bo) {
+        return toAjax(salesleadsService.updateLeads(bo));
+    }
+
+    /**
+     * 删除
+     */
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@PathVariable Long[] ids) {
+        return toAjax(salesleadsService.deleteWithValidByIds(Arrays.asList(ids), true));
+    }
+
+    /**
+     * 认领销售线索
+     */
+    @PostMapping("/claim")
+    public R<Void> claim(@RequestBody SalesleadsBo bo) {
+        return toAjax(salesleadsService.claimLeads(bo));
+    }
+
+    /**
+     * 转移销售线索
+     */
+    @PostMapping("/transfer")
+    public R<Void> transfer(@RequestBody SalesleadsBo bo) {
+        return toAjax(salesleadsService.transferLeads(bo));
+    }
+
+}

+ 66 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/TeamMemberController.java

@@ -0,0 +1,66 @@
+package org.dromara.customer.controller;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.domain.R;
+import org.dromara.common.web.core.BaseController;
+import org.dromara.customer.domain.bo.TeamMemberBo;
+import org.dromara.customer.domain.vo.TeamMemberVo;
+import org.dromara.customer.service.ITeamMemberService;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * 团队成员 前端控制器
+ *
+ * @author tys
+ * date  2026-04-17
+ */
+@RequiredArgsConstructor
+@RestController
+@RequestMapping("/teamMember")
+public class TeamMemberController extends BaseController {
+
+    private final ITeamMemberService teamMemberService;
+
+    /**
+     * 按对象编号查询团队成员列表
+     */
+    @GetMapping("/list/{objectNo}")
+    public R<List<TeamMemberVo>> list(@PathVariable String objectNo) {
+        return R.ok(teamMemberService.queryByObjectNo(objectNo));
+    }
+
+    /**
+     * 获取详细信息
+     */
+    @GetMapping("/{id}")
+    public R<TeamMemberVo> getInfo(@PathVariable Long id) {
+        return R.ok(teamMemberService.queryById(id));
+    }
+
+    /**
+     * 新增团队成员
+     */
+    @PostMapping
+    public R<Void> add(@RequestBody TeamMemberBo bo) {
+        return toAjax(teamMemberService.insertMember(bo));
+    }
+
+    /**
+     * 修改团队成员
+     */
+    @PutMapping
+    public R<Void> edit(@RequestBody TeamMemberBo bo) {
+        return toAjax(teamMemberService.updateMember(bo));
+    }
+
+    /**
+     * 删除团队成员
+     */
+    @DeleteMapping("/{ids}")
+    public R<Void> remove(@PathVariable Long[] ids) {
+        return toAjax(teamMemberService.deleteByIds(Arrays.asList(ids)));
+    }
+}

+ 58 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/controller/constant/CustomerConstants.java

@@ -0,0 +1,58 @@
+package org.dromara.customer.controller.constant;
+
+/**
+ * 客户管理模块常量
+ * @author tys
+ */
+public interface CustomerConstants {
+
+    /**
+     * 默认平台编码
+     */
+    String DEFAULT_PLATFORM = "crm";
+
+    /**
+     * 销售线索类型
+     */
+    String PROJECT_TYPE_LEADS = "销售线索";
+
+    /**
+     * 数据类型:销售线索/项目商机
+     */
+    Integer DATA_TYPE_LEADS = 1;
+
+    /**
+     * 操作类型:新增
+     */
+    Integer ACTION_TYPE_INSERT = 1;
+
+    /**
+     * 操作类型:修改
+     */
+    Integer ACTION_TYPE_UPDATE = 2;
+
+    /**
+     * 操作类型:删除
+     */
+    Integer ACTION_TYPE_DELETE = 3;
+
+    /**
+     * 团队角色:业务负责人
+     */
+    String TEAM_ROLE_LEADER = "1";
+
+    /**
+     * 模块:项目商机
+     */
+    String MODULE_SALES_LEADS = "项目商机";
+
+    /**
+     * 模块:团队成员
+     */
+    String MODULE_TEAM_MEMBER = "团队成员";
+
+    /**
+     * 模块:项目进度
+     */
+    String MODULE_PROJECT_PROGRESS = "项目进度";
+}

+ 106 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/CrmStaff.java

@@ -0,0 +1,106 @@
+package org.dromara.customer.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+import java.util.Date;
+
+/**
+ * CRM人员信息对象 crm_staff
+ * @author tys
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("crm_staff")
+public class CrmStaff extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 人员ID
+     */
+    @TableId(value = "staff_id")
+    private Long staffId;
+
+    /**
+     * 人员编码
+     */
+    private String staffCode;
+
+    /**
+     * 姓名
+     */
+    private String staffName;
+
+    /**
+     * 所属部门编码
+     */
+    private Long deptId;
+
+    /**
+     * 联系电话
+     */
+    private String phone;
+
+    /**
+     * 岗位编码
+     */
+    private Long postId;
+
+    /**
+     * 性别
+     */
+    private String sex;
+
+    /**
+     * 角色编码
+     */
+    private Long roleId;
+
+    /**
+     * 数据来源
+     */
+    private String dataSource;
+
+    /**
+     * 密码
+     */
+    private String password;
+
+    /**
+     * 有效期起始
+     */
+    private Date validFrom;
+
+    /**
+     * 有效期截止
+     */
+    private Date validTo;
+
+    /**
+     * 状态(0正常 1停用)
+     */
+    private String status;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 平台标识
+     */
+    private String platformCode;
+}

+ 67 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/CustomerDict.java

@@ -0,0 +1,67 @@
+package org.dromara.customer.domain;
+
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+import org.dromara.customer.domain.vo.CustomerDictVo;
+
+/**
+ * 通用字典对象 commondicc
+ *
+ * @author LionLi
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = CustomerDictVo.class)
+@TableName("commondicc")
+public class CustomerDict extends TenantEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId(value = "Id")
+    private Integer id;
+
+    /**
+     * 名称
+     */
+    @TableField("Name")
+    private String name;
+
+    /**
+     * 代码 (对应 dictType)
+     */
+    @TableField("Code")
+    private String code;
+
+    /**
+     * 代码索引 (对应 dictValue)
+     */
+    @TableField("CodeIndex")
+    private Integer codeIndex;
+
+    /**
+     * 值
+     */
+    @TableField("Value")
+    private String value;
+
+    /**
+     * 排序
+     */
+    @TableField("Sort")
+    private Integer sort;
+
+    /**
+     * 备注
+     */
+    @TableField("Remark")
+    private String remark;
+
+}

+ 91 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/OperationLog.java

@@ -0,0 +1,91 @@
+package org.dromara.customer.domain;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.io.Serial;
+
+/**
+ * 操作日志对象 operationlog
+ *
+ * @author tys
+ * @date 2026-04-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("operationlog")
+public class OperationLog extends TenantEntity {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键,自增ID
+     */
+    @TableId(value = "Id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 数据类型
+     */
+    @TableField("DataType")
+    private Integer dataType;
+
+    /**
+     * 对象编号
+     */
+    @TableField("ObjectNo")
+    private String objectNo;
+
+    /**
+     * 操作人
+     */
+    @TableField("Operator")
+    private String operator;
+
+    /**
+     * 操作类型
+     */
+    @TableField("ActionType")
+    private Integer actionType;
+
+    /**
+     * 目标对象
+     */
+    @TableField("TargetObject")
+    private String targetObject;
+
+    /**
+     * 操作模块
+     */
+    @TableField("ActionModule")
+    private Integer actionModule;
+
+    /**
+     * 操作详情
+     */
+    @TableField("ActionDetails")
+    private String actionDetails;
+
+    /**
+     * 客户端信息
+     */
+    @TableField("ClientInformation")
+    private String clientInformation;
+
+    /**
+     * 是否删除(0-否,1-是)
+     */
+    @TableLogic(value = "0", delval = "1")
+    @TableField("IsDelete")
+    private Integer isDelete = 0;
+
+    /**
+     * 平台标识
+     */
+    @TableField("platform_code")
+    private String platformCode;
+
+}

+ 205 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/Salesleads.java

@@ -0,0 +1,205 @@
+package org.dromara.customer.domain;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 销售线索对象 salesleads
+ *
+ * @author tys
+ * @date 2026-04-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@TableName("salesleads")
+public class Salesleads extends TenantEntity {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 是否线索(0:否, 1:是)
+     */
+    private Integer izClue;
+
+    /**
+     * 项目编号
+     */
+    private String projectNo;
+
+    /**
+     * 公司编号
+     */
+    private String companyNo;
+
+    /**
+     * 部门编号
+     */
+    private String deptNo;
+
+    /**
+     * 客户编号
+     */
+    private String customerNo;
+
+    /**
+     * 客户名称
+     */
+    private String customerName;
+
+    /**
+     * 项目名称
+     */
+    private String projectName;
+
+    /**
+     * 项目类型
+     */
+    private String projectType;
+
+    /**
+     * 项目预算
+     */
+    private BigDecimal projectBudget;
+
+    /**
+     * 中标率
+     */
+    private String winRate;
+
+    /**
+     * 批准日期
+     */
+    private Date approvalDate;
+
+    /**
+     * 预计完成时间
+     */
+    private Date expectedCompletionTime;
+
+    /**
+     * 项目级别
+     */
+    private Integer projectLevel;
+
+    /**
+     * 行业
+     */
+    private Long profession;
+
+    /**
+     * 业务人员
+     */
+    private String businessPersonnel;
+
+    /**
+     * 提交人
+     */
+    private String submitter;
+
+    /**
+     * 项目区域
+     */
+    private String projectArea;
+
+    /**
+     * 采购方式
+     */
+    private Integer procurementMethod;
+
+    /**
+     * 信息来源
+     */
+    private Integer infoSource;
+
+    /**
+     * 竞争对手
+     */
+    private String competitor;
+
+    /**
+     * 项目描述
+     */
+    private String projectDescription;
+
+    /**
+     * 档案编号
+     */
+    private String fileNo;
+
+    /**
+     * 负责人ID
+     */
+    private Long leader;
+
+    /**
+     * 负责人姓名
+     */
+    private String leaderName;
+
+    /**
+     * 状态
+     */
+    private String status;
+
+    /**
+     * 平台标识
+     */
+    private String platformCode;
+
+
+    /**
+     * 活动编号
+     */
+    private String activityNo;
+
+    /**
+     * 营销活动名称
+     */
+    private String marketingActivityName;
+
+    /**
+     * 产品支持
+     */
+    private String productSupport;
+
+
+    /**
+     * 项目进度
+     */
+    private String projectSchedule;
+
+    /**
+     * 公开招标截止日期
+     */
+    private Date publicBiddingDeadline;
+
+    /**
+     * 采购内容
+     */
+    private String purchaseContent;
+
+    /**
+     * 备注
+     */
+    private String remark;
+
+    /**
+     * 删除标志(0代表存在 2代表删除)
+     */
+    @TableLogic
+    private String delFlag;
+
+}

+ 72 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/TeamMember.java

@@ -0,0 +1,72 @@
+package org.dromara.customer.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableLogic;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.util.Date;
+
+/**
+ * 团队成员对象 team_member
+ *
+ * @author tys
+ * @date 2026-04-17
+ */
+@Data
+@TableName("team_member")
+public class TeamMember {
+
+    private static final long serialVersionUID = 1L;
+
+    /** 主键ID */
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 数据类型:标识对象所属的业务类型(如1-销售线索项目) */
+    private Integer dataType;
+
+    /** 对象编号:关联团队/项目的编号 */
+    private String objectNo;
+
+    /** 用户编号:关联用户表 */
+    private Long userNo;
+
+    /** 真实姓名:团队成员的姓名 */
+    private String realName;
+
+    /** 角色代码:团队成员在团队中的角色 */
+    private String roleCode;
+
+    /** 更新授权:0-无权限,1-有权限 */
+    private Integer updateAccredit;
+
+    /** 是否管理员:0-普通成员,1-管理员 */
+    private Integer izManager;
+
+    /** 创建组织ID */
+    private Long createOrgId;
+
+    /** 创建时间 */
+    private Date createTime;
+
+    /** 更新时间 */
+    private Date updateTime;
+
+    /** 创建人ID */
+    private Long createUserId;
+
+    /** 更新人ID */
+    private Long updateUserId;
+
+    /** 删除标记:0-未删除,1-已删除 */
+    @TableLogic(value = "0", delval = "1")
+    private Integer isDelete;
+
+    /** 租户ID */
+    private String tenantId;
+
+    /** 平台标识 */
+    private String platformCode;
+}

+ 72 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/OperationLogBo.java

@@ -0,0 +1,72 @@
+package org.dromara.customer.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.mybatis.core.domain.BaseEntity;
+import org.dromara.customer.domain.OperationLog;
+
+import java.io.Serializable;
+
+/**
+ * 操作日志业务对象 operationlog
+ *
+ * @author tys
+ * @date 2026-04-20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@AutoMapper(target = OperationLog.class, reverseConvertGenerate = true)
+public class OperationLogBo extends BaseEntity implements Serializable {
+
+    /**
+     * 主键,自增ID
+     */
+    private Long id;
+
+    /**
+     * 数据类型
+     */
+    private Integer dataType;
+
+    /**
+     * 对象编号
+     */
+    private String objectNo;
+
+    /**
+     * 操作人
+     */
+    private String operator;
+
+    /**
+     * 操作类型
+     */
+    private Integer actionType;
+
+    /**
+     * 目标对象
+     */
+    private String targetObject;
+
+    /**
+     * 操作模块
+     */
+    private Integer actionModule;
+
+    /**
+     * 操作详情
+     */
+    private String actionDetails;
+
+    /**
+     * 客户端信息
+     */
+    private String clientInformation;
+
+    /**
+     * 平台标识
+     */
+    private String platformCode;
+
+}

+ 132 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/SalesleadsBo.java

@@ -0,0 +1,132 @@
+package org.dromara.customer.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import org.dromara.common.tenant.core.TenantEntity;
+import org.dromara.customer.domain.Salesleads;
+
+import java.math.BigDecimal;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 销售线索/项目商机业务对象
+ *
+ * @author tys
+ * @date 2026-04-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+@Schema(description = "销售线索/项目商机业务对象")
+@AutoMapper(target = Salesleads.class, reverseConvertGenerate = false)
+public class SalesleadsBo extends TenantEntity {
+
+    @Schema(description = "主键ID")
+    private Long id;
+
+    @Schema(description = "是否线索(0:否, 1:是)")
+    private Integer izClue;
+
+    @Schema(description = "项目编号")
+    private String projectNo;
+
+    @Schema(description = "公司编号")
+    private String companyNo;
+
+    @Schema(description = "部门编号")
+    private String deptNo;
+
+    @Schema(description = "客户编号")
+    private String customerNo;
+
+    @Schema(description = "客户名称")
+    private String customerName;
+
+    @Schema(description = "项目名称")
+    private String projectName;
+
+    @Schema(description = "项目类型")
+    private String projectType;
+
+    @Schema(description = "项目预算")
+    private BigDecimal projectBudget;
+
+    @Schema(description = "中标率")
+    private String winRate;
+
+    @Schema(description = "批准日期")
+    private Date approvalDate;
+
+    @Schema(description = "预计完成时间")
+    private Date expectedCompletionTime;
+
+    @Schema(description = "项目级别")
+    private Integer projectLevel;
+
+    @Schema(description = "行业")
+    private Long profession;
+
+    @Schema(description = "业务人员")
+    private String businessPersonnel;
+
+    @Schema(description = "提交人")
+    private String submitter;
+
+    @Schema(description = "项目区域")
+    private String projectArea;
+
+    @Schema(description = "采购方式")
+    private Integer procurementMethod;
+
+    @Schema(description = "信息来源")
+    private Integer infoSource;
+
+    @Schema(description = "竞争对手")
+    private String competitor;
+
+    @Schema(description = "项目描述")
+    private String projectDescription;
+
+    @Schema(description = "档案编号")
+    private String fileNo;
+
+    @Schema(description = "负责人ID")
+    private Long leader;
+
+    @Schema(description = "负责人姓名")
+    private String leaderName;
+
+    @Schema(description = "状态")
+    private String status;
+
+    @Schema(description = "平台标识")
+    private String platformCode;
+
+
+    @Schema(description = "活动编号")
+    private String activityNo;
+
+    @Schema(description = "营销活动名称")
+    private String marketingActivityName;
+
+    @Schema(description = "产品支持")
+    private String productSupport;
+
+
+    @Schema(description = "项目进度")
+    private String projectSchedule;
+
+    @Schema(description = "公开招标截止日期")
+    private Date publicBiddingDeadline;
+
+    @Schema(description = "采购内容")
+    private String purchaseContent;
+
+    @Schema(description = "备注")
+    private String remark;
+
+    @Schema(description = "批量操作ID列表")
+    private List<Long> ids;
+}

+ 66 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/bo/TeamMemberBo.java

@@ -0,0 +1,66 @@
+package org.dromara.customer.domain.bo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.customer.domain.TeamMember;
+
+import java.util.Date;
+
+/**
+ * 团队成员业务对象
+ *
+ * @author tys
+ * @date 2026-04-17
+ */
+@Data
+@AutoMapper(target = TeamMember.class, reverseConvertGenerate = false)
+public class TeamMemberBo {
+
+    /** 主键ID */
+    private Long id;
+
+    /** 数据类型 */
+    private Integer dataType;
+
+    /** 对象编号 */
+    private String objectNo;
+
+    /** 用户编号 */
+    private Long userNo;
+
+    /** 真实姓名 */
+    private String realName;
+
+    /** 角色代码 */
+    private String roleCode;
+
+    /** 更新授权:0-无权限,1-有权限 */
+    private Integer updateAccredit;
+
+    /** 是否管理员:0-普通成员,1-管理员 */
+    private Integer izManager;
+
+    /** 创建组织ID */
+    private Long createOrgId;
+
+    /** 创建时间 */
+    private Date createTime;
+
+    /** 更新时间 */
+    private Date updateTime;
+
+    /** 创建人ID */
+    private Long createUserId;
+
+    /** 更新人ID */
+    private Long updateUserId;
+
+    /** 删除标记:0-未删除,1-已删除 */
+    private Integer isDelete;
+
+    /** 租户ID */
+    private String tenantId;
+
+    /** 平台标识 */
+    private String platformCode;
+}

+ 42 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CrmStaffVo.java

@@ -0,0 +1,42 @@
+package org.dromara.customer.domain.vo;
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.customer.domain.CrmStaff;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * CRM人员信息视图对象 crm_staff
+ * @author tys
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = CrmStaff.class)
+public class CrmStaffVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    @ExcelProperty(value = "人员ID")
+    private Long staffId;
+
+    @ExcelProperty(value = "人员编码")
+    private String staffCode;
+
+    @ExcelProperty(value = "姓名")
+    private String staffName;
+
+    @ExcelProperty(value = "联系电话")
+    private String phone;
+
+    @ExcelProperty(value = "性别")
+    private String sex;
+
+    @ExcelProperty(value = "状态")
+    private String status;
+
+}

+ 63 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/CustomerDictVo.java

@@ -0,0 +1,63 @@
+package org.dromara.customer.domain.vo;
+
+
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.customer.domain.CustomerDict;
+
+import java.io.Serial;
+import java.io.Serializable;
+
+/**
+ * 客户字典视图对象 crm_customer_dict
+ *
+ * @author LionLi
+ * @date 2024-04-07
+ */
+@Data
+@ExcelIgnoreUnannotated
+@AutoMapper(target = CustomerDict.class)
+public class CustomerDictVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键ID
+     */
+    @ExcelProperty(value = "主键ID")
+    private Long id;
+
+    /**
+     * 字典类型
+     */
+    @ExcelProperty(value = "字典类型")
+    private String dictType;
+
+    /**
+     * 显示名称
+     */
+    @ExcelProperty(value = "显示名称")
+    private String dictLabel;
+
+    /**
+     * 存储值
+     */
+    @ExcelProperty(value = "存储值")
+    private String dictValue;
+
+    /**
+     * 排序
+     */
+    @ExcelProperty(value = "排序")
+    private Integer orderNum;
+
+    /**
+     * 备注
+     */
+    @ExcelProperty(value = "备注")
+    private String remark;
+
+}

+ 87 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/OperationLogVo.java

@@ -0,0 +1,87 @@
+package org.dromara.customer.domain.vo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.customer.domain.OperationLog;
+
+import java.io.Serial;
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 操作日志视图对象 operationlog
+ *
+ * @author tys
+ * @date 2026-04-20
+ */
+@Data
+@AutoMapper(target = OperationLog.class)
+public class OperationLogVo implements Serializable {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键,自增ID
+     */
+    private Long id;
+
+    /**
+     * 数据类型
+     */
+    private Integer dataType;
+
+    /**
+     * 对象编号
+     */
+    private String objectNo;
+
+    /**
+     * 操作人
+     */
+    private String operator;
+
+    /**
+     * 操作类型
+     */
+    private Integer actionType;
+
+    /**
+     * 目标对象
+     */
+    private String targetObject;
+
+    /**
+     * 操作模块
+     */
+    private Integer actionModule;
+
+    /**
+     * 操作详情
+     */
+    private String actionDetails;
+
+    /**
+     * 客户端信息
+     */
+    private String clientInformation;
+
+    /**
+     * 创建时间
+     */
+    private Date createTime;
+
+    /**
+     * 创建者ID
+     */
+    private Long createBy;
+
+    /**
+     * 创建者姓名
+     */
+    @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy")
+    private String createByName;
+
+}

+ 160 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/SalesleadsVo.java

@@ -0,0 +1,160 @@
+package org.dromara.customer.domain.vo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.dromara.common.translation.annotation.Translation;
+import org.dromara.common.translation.constant.TransConstant;
+import org.dromara.customer.domain.Salesleads;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.util.Date;
+
+/**
+ * 销售线索/项目商机视图对象
+ *
+ * @author tys
+ * @date 2026-04-16
+ */
+@Data
+@Schema(description = "销售线索/项目商机视图对象")
+@AutoMapper(target = Salesleads.class)
+public class SalesleadsVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "主键ID")
+    private Long id;
+
+    @Schema(description = "是否线索(0:否, 1:是)")
+    private Integer izClue;
+
+    @Schema(description = "项目编号")
+    private String projectNo;
+
+    @Schema(description = "公司编号")
+    private String companyNo;
+
+    @Schema(description = "公司名称")
+    private String companyName;
+
+    @Schema(description = "部门编号")
+    private String deptNo;
+
+    @Schema(description = "客户编号")
+    private String customerNo;
+
+    @Schema(description = "客户名称")
+    private String customerName;
+
+    @Schema(description = "项目名称")
+    private String projectName;
+
+    @Schema(description = "项目类型")
+    private String projectType;
+
+    @Schema(description = "项目预算")
+    private BigDecimal projectBudget;
+
+    @Schema(description = "中标率")
+    private String winRate;
+
+    @Schema(description = "批准日期")
+    private Date approvalDate;
+
+    @Schema(description = "预计完成时间")
+    private Date expectedCompletionTime;
+
+    @Schema(description = "项目级别")
+    private Integer projectLevel;
+
+    @Schema(description = "行业")
+    private Long profession;
+
+    @Schema(description = "行业名称")
+    private String industry;
+
+    @Schema(description = "业务人员")
+    private String businessPersonnel;
+
+    @Schema(description = "提交人")
+    private String submitter;
+
+    @Schema(description = "项目区域")
+    private String projectArea;
+
+    @Schema(description = "采购方式")
+    private Integer procurementMethod;
+
+    @Schema(description = "信息来源")
+    private Integer infoSource;
+
+    @Schema(description = "竞争对手")
+    private String competitor;
+
+    @Schema(description = "项目描述")
+    private String projectDescription;
+
+    @Schema(description = "档案编号")
+    private String fileNo;
+
+    @Schema(description = "负责人ID")
+    private Long leader;
+
+    @Schema(description = "负责人姓名")
+    private String leaderName;
+
+    @Schema(description = "状态")
+    private String status;
+
+    @Schema(description = "平台标识")
+    private String platformCode;
+
+    @Schema(description = "活动编号")
+    private String activityNo;
+
+    @Schema(description = "营销活动名称")
+    private String marketingActivityName;
+
+    @Schema(description = "产品支持")
+    private String productSupport;
+
+    @Schema(description = "创建部门")
+    private Long createDept;
+
+    @Schema(description = "创建者")
+    private Long createBy;
+
+    @Schema(description = "创建者姓名")
+    @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy")
+    private String createByName;
+
+    @Schema(description = "更新者")
+    private Long updateBy;
+
+    @Schema(description = "更新者姓名")
+    @Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "updateBy")
+    private String updateByName;
+
+    @Schema(description = "租户编号")
+    private String tenantId;
+
+    @Schema(description = "项目进度")
+    private String projectSchedule;
+
+    @Schema(description = "公开招标截止日期")
+    private Date publicBiddingDeadline;
+
+    @Schema(description = "采购内容")
+    private String purchaseContent;
+
+    @Schema(description = "备注")
+    private String remark;
+
+    @Schema(description = "创建时间")
+    private Date createTime;
+
+    @Schema(description = "更新时间")
+    private Date updateTime;
+}

+ 76 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/domain/vo/TeamMemberVo.java

@@ -0,0 +1,76 @@
+package org.dromara.customer.domain.vo;
+
+import io.github.linpeilie.annotations.AutoMapper;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import org.dromara.customer.domain.TeamMember;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 团队成员视图对象
+ *
+ * @author tys
+ * @date 2026-04-17
+ */
+@Data
+@Schema(description = "团队成员视图对象")
+@AutoMapper(target = TeamMember.class)
+public class TeamMemberVo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @Schema(description = "主键ID")
+    private Long id;
+
+    @Schema(description = "数据类型")
+    private Integer dataType;
+
+    @Schema(description = "对象编号")
+    private String objectNo;
+
+    @Schema(description = "用户编号")
+    private Long userNo;
+
+    @Schema(description = "真实姓名")
+    private String realName;
+
+    @Schema(description = "角色代码")
+    private String roleCode;
+
+    /** 角色名称(由 SQL JOIN commondicc 直接返回) */
+    @Schema(description = "角色名称")
+    private String roleName;
+
+    @Schema(description = "更新授权:0-无权限,1-有权限")
+    private Integer updateAccredit;
+
+    /** 权限名称(由 SQL JOIN commondicc 直接返回) */
+    @Schema(description = "权限名称")
+    private String permission;
+
+    @Schema(description = "是否管理员:0-普通成员,1-管理员")
+    private Integer izManager;
+
+    @Schema(description = "创建组织ID")
+    private Long createOrgId;
+
+    @Schema(description = "创建时间")
+    private Date createTime;
+
+    @Schema(description = "更新时间")
+    private Date updateTime;
+
+    @Schema(description = "创建人ID")
+    private Long createUserId;
+
+    @Schema(description = "更新人ID")
+    private Long updateUserId;
+
+    @Schema(description = "租户ID")
+    private String tenantId;
+
+    @Schema(description = "平台标识")
+    private String platformCode;
+}

+ 15 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CrmStaffMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.customer.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.CrmStaff;
+import org.dromara.customer.domain.vo.CrmStaffVo;
+
+/**
+ * CRM人员信息Mapper接口
+ * @author tys
+ */
+@Mapper
+public interface CrmStaffMapper extends BaseMapperPlus<CrmStaff, CrmStaffVo> {
+
+}

+ 13 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/CustomerDictMapper.java

@@ -0,0 +1,13 @@
+package org.dromara.customer.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.CustomerDict;
+import org.dromara.customer.domain.vo.CustomerDictVo;
+
+/**
+ * 客户字典Mapper接口
+ *
+ * @author LionLi
+ */
+public interface CustomerDictMapper extends BaseMapperPlus<CustomerDict, CustomerDictVo> {
+}

+ 15 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/OperationLogMapper.java

@@ -0,0 +1,15 @@
+package org.dromara.customer.mapper;
+
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.OperationLog;
+import org.dromara.customer.domain.vo.OperationLogVo;
+
+/**
+ * 操作日志Mapper接口
+ *
+ * @author tys
+ * @date 2026-04-20
+ */
+public interface OperationLogMapper extends BaseMapperPlus<OperationLog, OperationLogVo> {
+
+}

+ 31 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/SalesleadsMapper.java

@@ -0,0 +1,31 @@
+package org.dromara.customer.mapper;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.Salesleads;
+import org.dromara.customer.domain.bo.SalesleadsBo;
+import org.dromara.customer.domain.vo.SalesleadsVo;
+
+import java.util.List;
+
+/**
+ * 销售线索/项目商机 Mapper 接口
+ *
+ * @author tys
+ * @date 2026-04-16
+ */
+public interface SalesleadsMapper extends BaseMapperPlus<Salesleads, SalesleadsVo> {
+
+    /**
+     * 查询列表
+     */
+    IPage<SalesleadsVo> selectSalesleadsList(Page<SalesleadsVo> page, @Param("bo") SalesleadsBo bo);
+
+    /**
+     * 查询所有列表
+     */
+    List<SalesleadsVo> selectSalesleadsList(@Param("bo") SalesleadsBo bo);
+
+}

+ 48 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/mapper/TeamMemberMapper.java

@@ -0,0 +1,48 @@
+package org.dromara.customer.mapper;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.apache.ibatis.annotations.Param;
+import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
+import org.dromara.customer.domain.TeamMember;
+import org.dromara.customer.domain.vo.TeamMemberVo;
+
+import java.util.List;
+
+/**
+ * 团队成员 Mapper 接口
+ *
+ * @author tys
+ * @date 2026-04-17
+ */
+public interface TeamMemberMapper extends BaseMapperPlus<TeamMember, TeamMemberVo> {
+
+    /**
+     * 按对象编号查询团队成员列表
+     */
+    List<TeamMemberVo> selectByObjectNo(@Param("objectNo") String objectNo);
+
+    /**
+     * 分页按对象编号查询
+     */
+    IPage<TeamMemberVo> selectPageByObjectNo(Page<TeamMemberVo> page, @Param("objectNo") String objectNo);
+
+    /**
+     * 按对象编号和用户编号查询(仅未删除)
+     */
+    TeamMemberVo selectByObjectNoAndUserNo(@Param("objectNo") String objectNo, @Param("userNo") Long userNo);
+
+    /**
+     * 按对象编号和用户编号查询(包含已删除)
+     */
+    TeamMemberVo selectByObjectNoAndUserNoWithDeleted(@Param("objectNo") String objectNo, @Param("userNo") Long userNo);
+
+    /**
+     * 恢复已删除的团队成员记录
+     */
+    int restoreMemberById(@Param("id") Long id, @Param("userNo") Long userNo,
+                          @Param("realName") String realName, @Param("roleCode") String roleCode,
+                          @Param("updateAccredit") Integer updateAccredit, @Param("izManager") Integer izManager,
+                          @Param("createUserId") Long createUserId, @Param("createOrgId") Long createOrgId,
+                          @Param("platformCode") String platformCode);
+}

+ 30 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICrmStaffService.java

@@ -0,0 +1,30 @@
+package org.dromara.customer.service;
+
+import org.dromara.customer.domain.vo.CrmStaffVo;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * CRM人员信息Service接口
+ * @author tys
+ */
+public interface ICrmStaffService {
+
+    /**
+     * 查询CRM人员下拉选项列表(只返回 staffId 和 staffName)
+     * 过滤状态为正常的员工
+     *
+     * @return 选项列表
+     */
+    List<CrmStaffVo> selectOptionList();
+
+    /**
+     * 根据人员ID集合批量查询人员姓名
+     *
+     * @param staffIds 人员ID集合
+     * @return ID和姓名的映射
+     */
+    Map<Long, String> selectStaffNameByIds(Collection<Long> staffIds);
+}

+ 22 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ICustomerDictService.java

@@ -0,0 +1,22 @@
+package org.dromara.customer.service;
+
+import org.dromara.customer.domain.vo.CustomerDictVo;
+
+import java.util.List;
+
+/**
+ * 客户字典Service接口
+ *
+ * @author LionLi
+ */
+public interface ICustomerDictService {
+    /**
+     * 根据字典类型获取数据列表
+     */
+    List<CustomerDictVo> selectListByType(String dictType);
+
+    /**
+     * 根据字典code获取通用字典数据列表
+     */
+    List<CustomerDictVo> selectListByCode(String dictCode);
+}

+ 41 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/IOperationLogService.java

@@ -0,0 +1,41 @@
+package org.dromara.customer.service;
+
+import org.dromara.customer.domain.bo.OperationLogBo;
+import org.dromara.customer.domain.vo.OperationLogVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 操作日志Service接口
+ * @author tys
+ */
+public interface IOperationLogService {
+
+    /**
+     * 查询操作日志列表
+     */
+    List<OperationLogVo> queryList(OperationLogBo bo);
+
+    /**
+     * 新增操作日志
+     */
+    Boolean insertByBo(OperationLogBo bo);
+
+    /**
+     * 批量删除操作日志
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 记录操作日志
+     *
+     * @param dataType      数据类型
+     * @param objectNo      对象编号
+     * @param actionType    操作类型
+     * @param actionModule  操作模块
+     * @param actionDetails 操作详情
+     * @param targetObject  目标对象
+     */
+    void recordLog(Integer dataType, String objectNo, Integer actionType, Integer actionModule, String actionDetails, String targetObject);
+}

+ 65 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ISalesleadsService.java

@@ -0,0 +1,65 @@
+package org.dromara.customer.service;
+
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.customer.domain.bo.SalesleadsBo;
+import org.dromara.customer.domain.vo.SalesleadsVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 销售线索/项目商机 Service 接口
+ *
+ * @author tys
+ * @date 2026-04-16
+ */
+public interface ISalesleadsService {
+
+    /**
+     * 查询
+     */
+    SalesleadsVo queryById(Long id);
+
+    /**
+     * 分页查询
+     */
+    TableDataInfo<SalesleadsVo> queryPageList(SalesleadsBo bo, PageQuery pageQuery);
+
+    /**
+     * 列表查询
+     */
+    List<SalesleadsVo> queryList(SalesleadsBo bo);
+
+    /**
+     * 新增
+     */
+    Boolean insertLeads(SalesleadsBo bo);
+
+    /**
+     * 修改
+     */
+    Boolean updateLeads(SalesleadsBo bo);
+
+    /**
+     * 校验并批量删除
+     */
+    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
+
+    /**
+     * 认领销售线索(设置项目负责人)
+     */
+    Boolean claimLeads(SalesleadsBo bo);
+
+    /**
+     * 转移销售线索(更换负责人)
+     */
+    Boolean transferLeads(SalesleadsBo bo);
+
+    /**
+     * 添加 ProjectType='销售线索' 的过滤条件
+     */
+    default void addProjectTypeFilter(SalesleadsBo bo) {
+        bo.setProjectType("销售线索");
+    }
+}

+ 46 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/ITeamMemberService.java

@@ -0,0 +1,46 @@
+package org.dromara.customer.service;
+
+import org.dromara.customer.domain.bo.TeamMemberBo;
+import org.dromara.customer.domain.vo.TeamMemberVo;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 团队成员 Service 接口
+ *
+ * @author tys
+ * @date 2026-04-17
+ */
+public interface ITeamMemberService {
+
+    /**
+     * 查询团队成员详情
+     */
+    TeamMemberVo queryById(Long id);
+
+    /**
+     * 按对象编号查询成员列表
+     */
+    List<TeamMemberVo> queryByObjectNo(String objectNo);
+
+    /**
+     * 新增团队成员
+     */
+    Boolean insertMember(TeamMemberBo bo);
+
+    /**
+     * 修改团队成员
+     */
+    Boolean updateMember(TeamMemberBo bo);
+
+    /**
+     * 删除团队成员
+     */
+    Boolean deleteByIds(Collection<Long> ids);
+
+    /**
+     * 新增或更新团队成员(根据 objectNo + userNo 判断)
+     */
+    Boolean insertOrUpdateMember(TeamMemberBo bo);
+}

+ 59 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CrmStaffServiceImpl.java

@@ -0,0 +1,59 @@
+package org.dromara.customer.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.dromara.customer.domain.CrmStaff;
+import org.dromara.customer.domain.vo.CrmStaffVo;
+import org.dromara.customer.mapper.CrmStaffMapper;
+import org.dromara.customer.service.ICrmStaffService;
+import org.springframework.stereotype.Service;
+
+import java.util.*;
+
+/**
+ * CRM人员信息Service业务层处理
+ * @author tys
+ */
+@RequiredArgsConstructor
+@Service
+public class CrmStaffServiceImpl implements ICrmStaffService {
+
+    private final CrmStaffMapper baseMapper;
+
+    @Override
+    public List<CrmStaffVo> selectOptionList() {
+        LambdaQueryWrapper<CrmStaff> lqw = Wrappers.lambdaQuery();
+        // 查询必要字段,为了兼容空数据,保留状态为 0 或者空的数据
+        lqw.select(CrmStaff::getStaffId, CrmStaff::getStaffName)
+           .and(w -> w.eq(CrmStaff::getStatus, "0").or().isNull(CrmStaff::getStatus))
+           .orderByAsc(CrmStaff::getStaffId);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    @Override
+    public Map<Long, String> selectStaffNameByIds(Collection<Long> staffIds) {
+        if (staffIds == null || staffIds.isEmpty()) {
+            return Collections.emptyMap();
+        }
+
+        LambdaQueryWrapper<CrmStaff> lqw = Wrappers.lambdaQuery();
+        lqw.select(CrmStaff::getStaffId, CrmStaff::getStaffName)
+           .in(CrmStaff::getStaffId, staffIds);
+
+        List<CrmStaffVo> staffList = baseMapper.selectVoList(lqw);
+        Map<Long, String> resultMap = new HashMap<>(staffIds.size());
+
+        for (Long id : staffIds) {
+            resultMap.put(id, null);
+        }
+        if (staffList != null) {
+            for (CrmStaffVo staff : staffList) {
+                if (staff.getStaffId() != null && staff.getStaffName() != null) {
+                    resultMap.put(staff.getStaffId(), staff.getStaffName());
+                }
+            }
+        }
+        return resultMap;
+    }
+}

+ 80 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/CustomerDictServiceImpl.java

@@ -0,0 +1,80 @@
+package org.dromara.customer.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import lombok.RequiredArgsConstructor;
+import org.dromara.customer.domain.CustomerDict;
+import org.dromara.customer.domain.vo.CustomerDictVo;
+import org.dromara.customer.mapper.CustomerDictMapper;
+import org.dromara.customer.service.ICustomerDictService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 客户字典Service业务层处理
+ */
+@RequiredArgsConstructor
+@Service
+public class CustomerDictServiceImpl implements ICustomerDictService {
+
+    private final CustomerDictMapper customerDictMapper;
+
+    @Override
+    public List<CustomerDictVo> selectListByType(String dictType) {
+        String typeLower = dictType.toLowerCase();
+        String code = dictType;
+        if (typeLower.endsWith("enterprise_type")) {
+            code = "Q0001";
+        } else if (typeLower.endsWith("cooperation_status")) {
+            code = "COOPERATION_STATUS";
+        } else if (typeLower.endsWith("schedule_status")) {
+            code = "schedule_status";
+        } else if (typeLower.endsWith("importance_level")) {
+            code = "importance_level";
+        } else if (typeLower.endsWith("visit_type")) {
+            code = "visit_type";
+        }
+
+        List<CustomerDict> list = customerDictMapper.selectList(new LambdaQueryWrapper<CustomerDict>()
+            .eq(CustomerDict::getCode, code)
+            .orderByAsc(CustomerDict::getSort));
+
+        return list.stream().map(d -> {
+            CustomerDictVo vo = new CustomerDictVo();
+            vo.setId(Long.valueOf(d.getId()));
+            vo.setDictLabel(d.getName());
+            // 优先取 Value 字段,如果为空则参考 CodeIndex (通常对应数值 ID)
+            String val = d.getValue();
+            if (val == null || val.isEmpty()) {
+                val = d.getCodeIndex() != null ? String.valueOf(d.getCodeIndex()) : "";
+            }
+            vo.setDictValue(val);
+            vo.setOrderNum(d.getSort());
+            vo.setRemark(d.getRemark());
+            return vo;
+        }).collect(Collectors.toList());
+    }
+
+    @Override
+    public List<CustomerDictVo> selectListByCode(String dictCode) {
+        List<CustomerDict> list = customerDictMapper.selectList(new LambdaQueryWrapper<CustomerDict>()
+            .eq(CustomerDict::getCode, dictCode)
+            .orderByAsc(CustomerDict::getSort));
+
+        return list.stream().map(d -> {
+            CustomerDictVo vo = new CustomerDictVo();
+            vo.setId(Long.valueOf(d.getId()));
+            vo.setDictLabel(d.getName());
+            // 优先取 Value 字段,如果为空则参考 CodeIndex
+            String val = d.getValue();
+            if (val == null || val.isEmpty()) {
+                val = d.getCodeIndex() != null ? String.valueOf(d.getCodeIndex()) : "";
+            }
+            vo.setDictValue(val);
+            vo.setOrderNum(d.getSort());
+            vo.setRemark(d.getRemark());
+            return vo;
+        }).collect(Collectors.toList());
+    }
+}

+ 84 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/OperationLogServiceImpl.java

@@ -0,0 +1,84 @@
+package org.dromara.customer.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.ServletUtils;
+import org.dromara.common.core.utils.StringUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.customer.domain.OperationLog;
+import org.dromara.customer.domain.bo.OperationLogBo;
+import org.dromara.customer.domain.vo.OperationLogVo;
+import org.dromara.customer.mapper.OperationLogMapper;
+import org.dromara.customer.service.IOperationLogService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 操作日志Service业务层处理
+ * @author tys
+ */
+@RequiredArgsConstructor
+@Service
+public class OperationLogServiceImpl implements IOperationLogService {
+
+    private final OperationLogMapper baseMapper;
+
+    @Override
+    public List<OperationLogVo> queryList(OperationLogBo bo) {
+        LambdaQueryWrapper<OperationLog> lqw = Wrappers.lambdaQuery();
+        // 如果传入了 dataType 则匹配,否则默认查销售线索相关的 (1)
+        lqw.eq(OperationLog::getDataType, bo.getDataType() != null ? bo.getDataType() : 1);
+        lqw.eq(StringUtils.isNotBlank(bo.getObjectNo()), OperationLog::getObjectNo, bo.getObjectNo());
+        lqw.eq(bo.getActionType() != null, OperationLog::getActionType, bo.getActionType());
+        lqw.eq(bo.getActionModule() != null, OperationLog::getActionModule, bo.getActionModule());
+        lqw.eq(StringUtils.isNotBlank(bo.getPlatformCode()), OperationLog::getPlatformCode, bo.getPlatformCode());
+        lqw.orderByDesc(OperationLog::getCreateTime);
+        return baseMapper.selectVoList(lqw);
+    }
+
+    @Override
+    public Boolean insertByBo(OperationLogBo bo) {
+        OperationLog add = MapstructUtils.convert(bo, OperationLog.class);
+        return baseMapper.insert(add) > 0;
+    }
+
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+
+    /**
+     * 记录操作日志
+     */
+    @Override
+    public void recordLog(Integer dataType, String objectNo, Integer actionType, Integer actionModule, String actionDetails, String targetObject) {
+        OperationLog log = new OperationLog();
+        log.setDataType(dataType);
+        log.setObjectNo(objectNo);
+        log.setActionType(actionType);
+        log.setActionModule(actionModule);
+        log.setActionDetails(actionDetails);
+        log.setTargetObject(targetObject);
+
+        // 提前获取 IP,防止异步失效
+        String ip = "127.0.0.1";
+        try {
+            ip = ServletUtils.getClientIP();
+        } catch (Exception e) {}
+        log.setClientInformation(ip);
+
+        // 获取操作人姓名
+        try {
+            log.setOperator(LoginHelper.getLoginUser().getNickname());
+        } catch (Exception e) {
+            log.setOperator("System");
+        }
+
+        log.setPlatformCode("crm");
+        baseMapper.insert(log);
+    }
+}

+ 191 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/SalesleadsServiceImpl.java

@@ -0,0 +1,191 @@
+package org.dromara.customer.service.impl;
+
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.core.utils.ObjectUtils;
+import org.dromara.common.mybatis.core.page.PageQuery;
+import org.dromara.common.mybatis.core.page.TableDataInfo;
+import org.dromara.common.redis.utils.SequenceUtils;
+
+import org.dromara.customer.controller.constant.CustomerConstants;
+import org.dromara.customer.domain.Salesleads;
+import org.dromara.customer.domain.bo.SalesleadsBo;
+import org.dromara.customer.domain.bo.TeamMemberBo;
+import org.dromara.customer.domain.vo.SalesleadsVo;
+import org.dromara.customer.mapper.SalesleadsMapper;
+import org.dromara.customer.service.IOperationLogService;
+import org.dromara.customer.service.ISalesleadsService;
+import org.dromara.customer.service.ITeamMemberService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.Duration;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 销售线索/项目商机 Service 业务层处理
+ *
+ * @author tys
+ * @date 2026-04-16
+ */
+@RequiredArgsConstructor
+@Service
+public class SalesleadsServiceImpl implements ISalesleadsService {
+
+    private static final String PROJECT_NO_KEY = "salesleads:project_no";
+
+    private final SalesleadsMapper baseMapper;
+    private final ITeamMemberService teamMemberService;
+    private final IOperationLogService operationLogService;
+
+    /**
+     * 查询
+     */
+    @Override
+    public SalesleadsVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 分页查询
+     */
+    @Override
+    public TableDataInfo<SalesleadsVo> queryPageList(SalesleadsBo bo, PageQuery pageQuery) {
+        IPage<SalesleadsVo> result = baseMapper.selectSalesleadsList(pageQuery.build(), bo);
+        return TableDataInfo.build(result);
+    }
+
+    /**
+     * 列表查询
+     */
+    @Override
+    public List<SalesleadsVo> queryList(SalesleadsBo bo) {
+        return baseMapper.selectSalesleadsList(bo);
+    }
+
+    /**
+     * 新增
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean insertLeads(SalesleadsBo bo) {
+        Salesleads add = MapstructUtils.convert(bo, Salesleads.class);
+        if (add.getProjectNo() == null || add.getProjectNo().isEmpty()) {
+            add.setProjectNo(SequenceUtils.nextPaddedIdStr(PROJECT_NO_KEY, Duration.ofDays(3650), 6));
+        }
+        if (add.getPlatformCode() == null || add.getPlatformCode().isEmpty()) {
+            add.setPlatformCode(CustomerConstants.DEFAULT_PLATFORM);
+        }
+        if (add.getProjectType() == null || add.getProjectType().isEmpty()) {
+            add.setProjectType(CustomerConstants.PROJECT_TYPE_LEADS);
+        }
+        boolean success = baseMapper.insert(add) > 0;
+        if (success && add.getLeader() != null) {
+            // 新增成功后,将负责人同步到团队成员表
+            syncLeaderToTeamMember(add.getId(), add.getLeader(), add.getLeaderName());
+        }
+        return success;
+    }
+
+    /**
+     * 修改
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean updateLeads(SalesleadsBo bo) {
+        Salesleads oldData = baseMapper.selectById(bo.getId());
+        Salesleads update = MapstructUtils.convert(bo, Salesleads.class);
+        boolean success = baseMapper.updateById(update) > 0;
+        if (success && oldData != null) {
+            if (!ObjectUtils.equals(oldData.getProjectSchedule(), bo.getProjectSchedule())) {
+                operationLogService.recordLog(CustomerConstants.DATA_TYPE_LEADS, String.valueOf(bo.getId()), CustomerConstants.ACTION_TYPE_UPDATE, null, CustomerConstants.MODULE_PROJECT_PROGRESS, bo.getProjectSchedule());
+            }
+        }
+        if (success && bo.getLeader() != null) {
+            // 负责人变更时,同步更新团队成员
+            if (oldData == null || !ObjectUtils.equals(oldData.getLeader(), bo.getLeader())) {
+                syncLeaderToTeamMember(bo.getId(), bo.getLeader(), bo.getLeaderName());
+            }
+        }
+        return success;
+    }
+
+    /**
+     * 批量删除
+     */
+    @Override
+    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
+        return baseMapper.deleteByIds(ids) > 0;
+    }
+
+    /**
+     * 认领销售线索 - 设置项目负责人(支持批量)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean claimLeads(SalesleadsBo bo) {
+        List<Long> ids = bo.getIds();
+        if (ids == null || ids.isEmpty()) {
+            if (bo.getId() == null) return false;
+            ids = List.of(bo.getId());
+        }
+        // 逐条更新(批量认领场景通常数据量不大)
+        boolean result = true;
+        for (Long id : ids) {
+            Salesleads update = new Salesleads();
+            update.setId(id);
+            update.setLeader(bo.getLeader());
+            update.setLeaderName(bo.getLeaderName());
+            result = result && baseMapper.updateById(update) > 0;
+            if (result) {
+                syncLeaderToTeamMember(id, bo.getLeader(), bo.getLeaderName());
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 转移销售线索 - 更换负责人(支持批量)
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean transferLeads(SalesleadsBo bo) {
+        List<Long> ids = bo.getIds();
+        if (ids == null || ids.isEmpty()) {
+            if (bo.getId() == null) return false;
+            ids = List.of(bo.getId());
+        }
+        boolean result = true;
+        for (Long id : ids) {
+            Salesleads update = new Salesleads();
+            update.setId(id);
+            update.setLeader(bo.getLeader());
+            update.setLeaderName(bo.getLeaderName());
+            result = result && baseMapper.updateById(update) > 0;
+            if (result) {
+                syncLeaderToTeamMember(id, bo.getLeader(), bo.getLeaderName());
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 同步负责人到团队成员表
+     * 如果该用户已存在则更新为负责人,否则新增
+     */
+    private void syncLeaderToTeamMember(Long objectNo, Long leaderId, String leaderName) {
+        if (leaderId == null) return;
+        TeamMemberBo memberBo = new TeamMemberBo();
+        memberBo.setDataType(CustomerConstants.DATA_TYPE_LEADS);
+        memberBo.setObjectNo(String.valueOf(objectNo));
+        memberBo.setUserNo(leaderId);
+        memberBo.setRealName(leaderName);
+        memberBo.setRoleCode(CustomerConstants.TEAM_ROLE_LEADER); // 业务负责人的字典值
+        memberBo.setIzManager(1);
+        memberBo.setUpdateAccredit(1);
+        teamMemberService.insertOrUpdateMember(memberBo);
+    }
+
+}

+ 158 - 0
ruoyi-modules/ruoyi-customer/src/main/java/org/dromara/customer/service/impl/TeamMemberServiceImpl.java

@@ -0,0 +1,158 @@
+package org.dromara.customer.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import org.dromara.common.core.utils.MapstructUtils;
+import org.dromara.common.satoken.utils.LoginHelper;
+import org.dromara.customer.controller.constant.CustomerConstants;
+import org.dromara.customer.domain.TeamMember;
+import org.dromara.customer.domain.bo.TeamMemberBo;
+import org.dromara.customer.domain.vo.TeamMemberVo;
+import org.dromara.customer.mapper.TeamMemberMapper;
+import org.dromara.customer.service.IOperationLogService;
+import org.dromara.customer.service.ITeamMemberService;
+import org.springframework.stereotype.Service;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 团队成员 Service 业务层处理
+ *
+ * @author tys
+ * @date 2026-04-17
+ */
+@RequiredArgsConstructor
+@Service
+public class TeamMemberServiceImpl implements ITeamMemberService {
+
+    private final TeamMemberMapper baseMapper;
+    private final IOperationLogService operationLogService;
+
+    /**
+     * 查询
+     */
+    @Override
+    public TeamMemberVo queryById(Long id) {
+        return baseMapper.selectVoById(id);
+    }
+
+    /**
+     * 按对象编号查询成员列表
+     */
+    @Override
+    public List<TeamMemberVo> queryByObjectNo(String objectNo) {
+        return baseMapper.selectByObjectNo(objectNo);
+    }
+
+    /**
+     * 新增
+     */
+    @Override
+    public Boolean insertMember(TeamMemberBo bo) {
+        // 查询是否存在(包括已删除的记录)
+        if (bo.getObjectNo() != null && bo.getUserNo() != null) {
+            TeamMemberVo existing = baseMapper.selectByObjectNoAndUserNoWithDeleted(bo.getObjectNo(), bo.getUserNo());
+            if (existing != null) {
+                // 记录已存在,使用自定义SQL恢复(绕过逻辑删除限制)
+                String platformCode = bo.getPlatformCode();
+                if (platformCode == null || platformCode.isEmpty()) {
+                    platformCode = CustomerConstants.DEFAULT_PLATFORM;
+                }
+                boolean restored = baseMapper.restoreMemberById(
+                    existing.getId(),
+                    bo.getUserNo(),
+                    bo.getRealName(),
+                    bo.getRoleCode(),
+                    bo.getUpdateAccredit(),
+                    bo.getIzManager(),
+                    LoginHelper.getUserId(),
+                    LoginHelper.getLoginUser().getDeptId(),
+                    platformCode
+                ) > 0;
+                if (restored) {
+                    Integer dataType = bo.getDataType() != null ? bo.getDataType() : CustomerConstants.DATA_TYPE_LEADS;
+                    operationLogService.recordLog(dataType, bo.getObjectNo(), CustomerConstants.ACTION_TYPE_INSERT, null, CustomerConstants.MODULE_TEAM_MEMBER, bo.getRealName());
+                }
+                return restored;
+            }
+        }
+
+        // 不存在,新增
+        TeamMember add = MapstructUtils.convert(bo, TeamMember.class);
+        add.setCreateUserId(LoginHelper.getUserId());
+        add.setCreateOrgId(LoginHelper.getLoginUser().getDeptId());
+        add.setIsDelete(0);
+        if (add.getPlatformCode() == null || add.getPlatformCode().isEmpty()) {
+            add.setPlatformCode(CustomerConstants.DEFAULT_PLATFORM);
+        }
+        boolean flag = baseMapper.insert(add) > 0;
+        if (flag) {
+            Integer dataType = bo.getDataType() != null ? bo.getDataType() : CustomerConstants.DATA_TYPE_LEADS;
+            operationLogService.recordLog(dataType, bo.getObjectNo(), CustomerConstants.ACTION_TYPE_INSERT, null, CustomerConstants.MODULE_TEAM_MEMBER, bo.getRealName());
+        }
+        return flag;
+    }
+
+    /**
+     * 修改
+     */
+    @Override
+    public Boolean updateMember(TeamMemberBo bo) {
+        // 如果 objectNo 为空,先从原记录补全,防止记录操作日志失败
+        if (bo.getObjectNo() == null && bo.getId() != null) {
+            TeamMemberVo original = baseMapper.selectVoById(bo.getId());
+            if (original != null) {
+                bo.setObjectNo(original.getObjectNo());
+                bo.setDataType(original.getDataType());
+                if (bo.getRealName() == null) {
+                    bo.setRealName(original.getRealName());
+                }
+            }
+        }
+        TeamMember update = MapstructUtils.convert(bo, TeamMember.class);
+        boolean flag = baseMapper.updateById(update) > 0;
+        if (flag) {
+            Integer dataType = bo.getDataType() != null ? bo.getDataType() : CustomerConstants.DATA_TYPE_LEADS;
+            operationLogService.recordLog(dataType, bo.getObjectNo(), CustomerConstants.ACTION_TYPE_UPDATE, null, CustomerConstants.MODULE_TEAM_MEMBER, bo.getRealName());
+        }
+        return flag;
+    }
+
+    /**
+     * 删除
+     */
+    @Override
+    public Boolean deleteByIds(Collection<Long> ids) {
+        if (ids != null && !ids.isEmpty()) {
+            Long firstId = ids.iterator().next();
+            TeamMemberVo member = baseMapper.selectVoById(firstId);
+            boolean flag = baseMapper.deleteByIds(ids) > 0;
+            if (flag && member != null) {
+                Integer dataType = member.getDataType() != null ? member.getDataType() : CustomerConstants.DATA_TYPE_LEADS;
+                operationLogService.recordLog(dataType, member.getObjectNo(), CustomerConstants.ACTION_TYPE_DELETE, null, CustomerConstants.MODULE_TEAM_MEMBER, member.getRealName());
+            }
+            return flag;
+        }
+        return false;
+    }
+
+    /**
+     * 新增或更新团队成员(根据 objectNo + userNo 判断)
+     * 如果该用户已存在则更新,否则新增
+     */
+    @Override
+    public Boolean insertOrUpdateMember(TeamMemberBo bo) {
+        if (bo.getObjectNo() == null || bo.getUserNo() == null) {
+            return false;
+        }
+        TeamMemberVo existing = baseMapper.selectByObjectNoAndUserNo(bo.getObjectNo(), bo.getUserNo());
+        if (existing != null) {
+            // 已存在,更新
+            bo.setId(existing.getId());
+            return updateMember(bo);
+        } else {
+            // 不存在,新增
+            return insertMember(bo);
+        }
+    }
+}

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

@@ -0,0 +1,46 @@
+<?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.customer.mapper.SalesleadsMapper">
+
+    <select id="selectSalesleadsList" resultType="org.dromara.customer.domain.vo.SalesleadsVo">
+        SELECT s.*, cc.company_name AS companyName, ic.industry_category_name AS industry
+        FROM salesleads s
+        LEFT JOIN sys_company cc ON s.company_no COLLATE utf8mb4_unicode_ci = cc.company_code COLLATE utf8mb4_unicode_ci AND cc.del_flag = '0'
+        LEFT JOIN industry_category ic ON s.profession = ic.id AND ic.del_flag = '0'
+        <where>
+            s.del_flag = '0'
+            <if test="bo.izClue != null">
+                AND s.iz_clue = #{bo.izClue}
+            </if>
+            <if test="bo.projectName != null and bo.projectName != ''">
+                AND s.project_name LIKE concat('%', #{bo.projectName}, '%')
+            </if>
+            <if test="bo.projectNo != null and bo.projectNo != ''">
+                AND s.project_no = #{bo.projectNo}
+            </if>
+            <if test="bo.customerName != null and bo.customerName != ''">
+                AND s.customer_name LIKE concat('%', #{bo.customerName}, '%')
+            </if>
+            <if test="bo.status != null and bo.status != ''">
+                AND s.status = #{bo.status}
+            </if>
+            <if test="bo.projectType != null and bo.projectType != ''">
+                AND s.project_type = #{bo.projectType}
+            </if>
+            <if test="bo.companyNo != null and bo.companyNo != ''">
+                AND s.company_no = #{bo.companyNo}
+            </if>
+            <if test="bo.leader != null">
+                AND s.leader = #{bo.leader}
+            </if>
+            <if test="bo.deptNo != null and bo.deptNo != ''">
+                AND s.dept_no = #{bo.deptNo}
+            </if>
+            ${bo.params.dataScope}
+        </where>
+        ORDER BY create_time DESC
+    </select>
+
+</mapper>

+ 71 - 0
ruoyi-modules/ruoyi-customer/src/main/resources/mapper/customer/TeamMemberMapper.xml

@@ -0,0 +1,71 @@
+<?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.customer.mapper.TeamMemberMapper">
+
+    <!-- 按对象编号查询成员列表(JOIN commondicc 翻译角色名称和权限名称) -->
+    <select id="selectByObjectNo" resultType="org.dromara.customer.domain.vo.TeamMemberVo">
+        SELECT
+            tm.*,
+            cd_role.Name AS roleName,
+            cd_perm.Name AS permission
+        FROM team_member tm
+        LEFT JOIN commondicc cd_role
+            ON cd_role.Code COLLATE utf8mb4_general_ci = 'T0001'
+            AND cd_role.Value COLLATE utf8mb4_general_ci = CONVERT(tm.role_code USING utf8mb4) COLLATE utf8mb4_general_ci
+            AND cd_role.del_flag = '0'
+        LEFT JOIN commondicc cd_perm
+            ON cd_perm.Code COLLATE utf8mb4_general_ci = 'team_permission'
+            AND cd_perm.Value COLLATE utf8mb4_general_ci = CAST(tm.update_accredit AS CHAR) COLLATE utf8mb4_general_ci
+            AND cd_perm.del_flag = '0'
+        WHERE tm.object_no = #{objectNo} AND tm.is_delete = 0
+        ORDER BY tm.iz_manager DESC, tm.create_time ASC
+    </select>
+
+    <select id="selectPageByObjectNo" resultType="org.dromara.customer.domain.vo.TeamMemberVo">
+        SELECT
+            tm.*,
+            cd_role.Name AS roleName,
+            cd_perm.Name AS permission
+        FROM team_member tm
+        LEFT JOIN commondicc cd_role
+            ON cd_role.Code COLLATE utf8mb4_general_ci = 'T0001'
+            AND cd_role.Value COLLATE utf8mb4_general_ci = CONVERT(tm.role_code USING utf8mb4) COLLATE utf8mb4_general_ci
+            AND cd_role.del_flag = '0'
+        LEFT JOIN commondicc cd_perm
+            ON cd_perm.Code COLLATE utf8mb4_general_ci = 'team_permission'
+            AND cd_perm.Value COLLATE utf8mb4_general_ci = CAST(tm.update_accredit AS CHAR) COLLATE utf8mb4_general_ci
+            AND cd_perm.del_flag = '0'
+        WHERE tm.object_no = #{objectNo} AND tm.is_delete = 0
+        ORDER BY tm.iz_manager DESC, tm.create_time ASC
+    </select>
+
+    <select id="selectByObjectNoAndUserNo" resultType="org.dromara.customer.domain.vo.TeamMemberVo">
+        SELECT * FROM team_member
+        WHERE object_no = #{objectNo} AND user_no = #{userNo} AND is_delete = 0
+        LIMIT 1
+    </select>
+
+    <select id="selectByObjectNoAndUserNoWithDeleted" resultType="org.dromara.customer.domain.vo.TeamMemberVo">
+        SELECT * FROM team_member
+        WHERE object_no = #{objectNo} AND user_no = #{userNo}
+        LIMIT 1
+    </select>
+
+    <update id="restoreMemberById">
+        UPDATE team_member SET 
+            is_delete = 0,
+            user_no = #{userNo},
+            real_name = #{realName},
+            role_code = #{roleCode},
+            update_accredit = #{updateAccredit},
+            iz_manager = #{izManager},
+            create_user_id = #{createUserId},
+            create_org_id = #{createOrgId},
+            platform_code = #{platformCode},
+            update_time = NOW()
+        WHERE id = #{id}
+    </update>
+
+</mapper>