From 5996173abd7df0a30cb738b4471eb4d7df712a9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E4=B9=90=E8=A8=80?= Date: Wed, 18 Mar 2026 16:39:23 +0800 Subject: [PATCH] =?UTF-8?q?0318-=E6=B5=B7=E5=AE=81=E8=8F=9C=E5=8D=95?= =?UTF-8?q?=E8=B0=83=E6=95=B4+=E5=8C=97=E4=BB=91=E5=AE=A2=E7=BE=A4?= =?UTF-8?q?=E9=83=A8=E5=88=86=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 187 +++++ .../group/controller/CustGroupController.java | 32 +- .../controller/CustGroupMemberController.java | 54 ++ .../domain/dto/CustGroupMemberQueryDTO.java | 27 + .../ruoyi/group/domain/entity/CustGroup.java | 10 - .../group/domain/entity/CustGroupMember.java | 2 - .../ruoyi/group/domain/vo/CustGroupVO.java | 49 +- .../ruoyi/group/mapper/CustGroupMapper.java | 10 + .../group/mapper/CustGroupMemberMapper.java | 21 +- .../service/ICustGroupMemberService.java | 32 + .../group/service/ICustGroupService.java | 33 +- .../impl/CustGroupMemberServiceImpl.java | 57 ++ .../service/impl/CustGroupServiceImpl.java | 560 +++++++++------ .../mapper/group/CustGroupMapper.xml | 29 + .../mapper/group/CustGroupMemberMapper.xml | 41 ++ .../cmpm/controller/GridCmpmController.java | 10 + .../ruoyi/ibs/cmpm/mapper/GridCmpmMapper.java | 30 + .../ibs/cmpm/service/GridCmpmService.java | 22 + .../draw/controller/DrawGridController.java | 10 + .../mapper/DrawGridCustUserUnbindMapper.java | 10 + .../mapper/DrawGridShapeRelateMapper.java | 19 + .../ibs/draw/service/DrawGridService.java | 7 + .../service/impl/DrawGridServiceImpl.java | 9 + .../controller/RegionGridListController.java | 11 +- .../ibs/grid/domain/vo/RegionGridGroupVO.java | 28 + .../ibs/grid/mapper/RegionCustUserMapper.java | 7 + .../ibs/grid/mapper/RegionGridMapper.java | 7 + .../grid/service/RegionGridListService.java | 18 +- .../impl/RegionGridListServiceImpl.java | 39 +- .../ibs/list/domain/CustInfoBusinessVo.java | 24 + .../ibs/list/domain/Ent9vPortraitOrc.java | 66 ++ .../ibs/list/domain/NineVFinalInfoOrc.java | 226 ++++++ .../list/mapper/Ent9vPortraitOrcMapper.java | 13 + .../list/mapper/NineVFinalInfoOrcMapper.java | 13 + .../impl/CustInfoBusinessServiceImpl.java | 19 + .../task/controller/WorkRecordController.java | 11 + .../ibs/task/mapper/WorkRecordMapper.java | 8 + .../ibs/task/service/WorkRecordService.java | 7 + .../service/impl/WorkRecordServiceImpl.java | 19 + .../resources/mapper/WorkRecordMapper.xml | 14 +- .../resources/mapper/cmpm/GridCmpmMapper.xml | 22 + .../mapper/draw/DrawGridCustMapper.xml | 11 +- .../mapper/grid/RegionGridCustUserMapper.xml | 26 + .../mapper/grid/RegionGridMapper.xml | 22 + ruoyi-ui/CLAUDE.md | 168 +++++ ruoyi-ui/src/api/grid/list/gridlist.js | 9 + ruoyi-ui/src/api/group/custGroup.js | 137 ++++ ruoyi-ui/src/api/system/home.js | 8 + ruoyi-ui/src/router/index.js | 11 + ruoyi-ui/src/store/modules/user.js | 1 + .../src/views/grid/charts/customer/index.vue | 22 +- .../customerPerformance/constant-info.js | 36 +- .../grid/charts/customerPerformance/index.vue | 27 +- .../custGroup/components/create-dialog.vue | 680 ++++++++++++++++++ ruoyi-ui/src/views/group/custGroup/detail.vue | 187 +++++ ruoyi-ui/src/views/group/custGroup/index.vue | 288 ++++++++ ruoyi-ui/src/views/index.vue | 58 +- .../views/taskManage/PADvisitRecord/index.vue | 70 +- .../views/taskManage/listStatistics/index.vue | 25 +- .../src/views/taskManage/taskList/index.vue | 61 +- .../src/views/workmng/warningtask/index.vue | 190 +++-- 61 files changed, 3388 insertions(+), 462 deletions(-) create mode 100644 CLAUDE.md create mode 100644 ibs-group/src/main/java/com/ruoyi/group/controller/CustGroupMemberController.java create mode 100644 ibs-group/src/main/java/com/ruoyi/group/domain/dto/CustGroupMemberQueryDTO.java create mode 100644 ibs-group/src/main/java/com/ruoyi/group/service/ICustGroupMemberService.java create mode 100644 ibs-group/src/main/java/com/ruoyi/group/service/impl/CustGroupMemberServiceImpl.java create mode 100644 ibs-group/src/main/resources/mapper/group/CustGroupMemberMapper.xml create mode 100644 ibs/src/main/java/com/ruoyi/ibs/grid/domain/vo/RegionGridGroupVO.java create mode 100644 ibs/src/main/java/com/ruoyi/ibs/list/domain/Ent9vPortraitOrc.java create mode 100644 ibs/src/main/java/com/ruoyi/ibs/list/domain/NineVFinalInfoOrc.java create mode 100644 ibs/src/main/java/com/ruoyi/ibs/list/mapper/Ent9vPortraitOrcMapper.java create mode 100644 ibs/src/main/java/com/ruoyi/ibs/list/mapper/NineVFinalInfoOrcMapper.java create mode 100644 ruoyi-ui/CLAUDE.md create mode 100644 ruoyi-ui/src/api/group/custGroup.js create mode 100644 ruoyi-ui/src/views/group/custGroup/components/create-dialog.vue create mode 100644 ruoyi-ui/src/views/group/custGroup/detail.vue create mode 100644 ruoyi-ui/src/views/group/custGroup/index.vue diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..0cfe942 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,187 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a full-stack banking management system (数字支行辅助管理系统) built on the RuoYi framework v3.8.8. It provides grid-based customer relationship management, customer grouping, visit tracking, and performance statistics for banking institutions. + +- **Backend**: Spring Boot 2.5.14 + MyBatis + MySQL + Redis + JWT +- **Frontend**: Vue 2.6.12 + Element UI 2.15.14 +- **Java Version**: 1.8 + +## Architecture + +### Backend Module Structure + +The backend is a multi-module Maven project with the following modules: + +``` +ruoyi/ +├── ruoyi-admin/ # Web entry point, main application (RuoYiApplication) +├── ruoyi-framework/ # Core framework (security, config, interceptors) +├── ruoyi-system/ # System management (users, roles, menus, depts) +├── ruoyi-common/ # Common utilities, annotations, domain classes +├── ruoyi-quartz/ # Scheduled task management +├── ruoyi-generator/ # Code generation tools +├── ibs/ # Main business module: grid customer management (网格营销) +└── ibs-group/ # Customer group management module (客户分组) +``` + +### Business Domain Structure + +Each business module (e.g., `ibs`, `ibs-group`) follows this package structure: + +``` +com.ruoyi./ +├── controller/ # REST controllers (handle HTTP requests) +├── service/ # Business logic layer +├── mapper/ # MyBatis mappers (database access) +├── domain/ +│ ├── entity/ # Database entities +│ ├── dto/ # Data Transfer Objects (request) +│ └── vo/ # View Objects (response) +└── handler/ # Custom MyBatis type handlers +``` + +### Frontend Structure + +``` +ruoyi-ui/ +├── src/ +│ ├── api/ # API request modules (organized by feature) +│ ├── views/ # Page components +│ ├── components/ # Reusable components +│ ├── store/ # Vuex state management +│ ├── router/ # Vue Router configuration +│ ├── utils/ # Utility functions +│ └── directive/ # Custom Vue directives +``` + +## Build and Run Commands + +### Backend (Maven) + +```bash +# Clean build artifacts +cd bin && clean.bat + +# Package the project (creates JAR in ruoyi-admin/target/) +cd bin && package.bat + +# Run the backend server (requires packaged JAR) +cd bin && run.bat + +# Or run directly with Maven from ruoyi-admin/ +mvn spring-boot:run + +# The main class is: com.ruoyi.RuoYiApplication +# Default port: 8080 +``` + +### Frontend (npm/Vue CLI) + +```bash +cd ruoyi-ui + +# Development server (runs on port 80) +npm run dev + +# For older Node.js versions with OpenSSL issues +npm run dev_t + +# Build for production +npm run build:prod + +# Build for staging +npm run build:stage + +# Build for pre-production +npm run build:pre + +# Lint code +npm run lint +``` + +## Development Configuration + +### Application Profiles + +Backend uses Spring profiles located in `ruoyi-admin/src/main/resources/`: +- `application.yml` - Base configuration +- `application-dev.yml` - Development environment +- `application-uat.yml` - UAT environment +- `application-pre.yml` - Pre-production +- `application-pro.yml` - Production + +Active profile is set in `application.yml` (default: `dev`). + +### Frontend API Proxy + +The Vue dev server proxies API requests to `http://localhost:8080`: +- Frontend dev server: `http://localhost:80` +- API requests: `/dev-api/*` → `http://localhost:8080/*` + +## Key Patterns and Conventions + +### Backend Code Patterns + +1. **Controller-Service-Mapper Layering** + - Controllers handle HTTP requests/responses, use `@RestController` + - Services contain business logic, use `@Service` + - Mappers use MyBatis annotations or XML files in `resources/mapper/` + +2. **DTO/VO Pattern** + - DTOs (Data Transfer Objects) for incoming requests + - VOs (View Objects) for outgoing responses + - Entities map directly to database tables + +3. **Security & Authentication** + - JWT-based authentication via `TokenService` + - Role-based access: `head`, `branch`, `outlet`, `manager` + - Use `SecurityUtils` for current user context: `SecurityUtils.getUsername()`, `SecurityUtils.getDeptId()`, `SecurityUtils.userRole()` + +4. **Pagination** + - Uses `PageHelper` for database pagination + - Controllers return `TableDataInfo` with `total` and `rows` + +5. **Caching** + - Uses `RedisCache` for Redis operations + - Cache keys often follow pattern: `{module}:{feature}:{key}` + +### Frontend Code Patterns + +1. **API Modules** + - Each feature has a dedicated API file in `src/api/` + - Uses `request()` wrapper around axios + - API base URL is configured in `src/utils/request.js` + +2. **Vuex Store** + - Modules in `src/store/modules/`: user, app, permission, settings, tagsView + - State persists via `vuex-persistedstate` + +3. **Permission Directives** + - `v-hasPermi` for button-level permissions + - `v-hasRole` for role-based display + +## Common Business Concepts + +- **网格**: Grid-based territory management for customer assignment +- **客户经理**: Relationship managers assigned to customers +- **客户星级**: Customer rating levels (5星, 4星, 3星, 2星, 1星, 基础, 长尾) +- **AUM (Asset Under Management)**: Customer assets, tracked with monthly averages +- **PAD走访**: Mobile visit records for customer interactions + +## MyBatis Configuration + +- Mapper XML files: `classpath*:mapper/**/*Mapper.xml` +- Type aliases: `com.ruoyi.**.domain` +- Custom type handlers: `com.ruoyi.ibs.handler` +- Pagination: `pagehelper` with MySQL dialect + +## Notes + +- The project uses Chinese comments and variable names in many places +- Scheduled tasks use Quartz via `ruoyi-quartz` module +- File upload path configured in `application.yml`: `ruoyi.profile` +- Swagger/Knife4j API docs available when running diff --git a/ibs-group/src/main/java/com/ruoyi/group/controller/CustGroupController.java b/ibs-group/src/main/java/com/ruoyi/group/controller/CustGroupController.java index 0150e1e..0179481 100644 --- a/ibs-group/src/main/java/com/ruoyi/group/controller/CustGroupController.java +++ b/ibs-group/src/main/java/com/ruoyi/group/controller/CustGroupController.java @@ -50,14 +50,14 @@ public class CustGroupController extends BaseController { } /** - * 获取客群详情 + * 根据ID查询客群详情 */ - @ApiOperation("获取客群详情") - @Log(title = "客群管理-获取客群详情") + @ApiOperation("根据ID查询客群详情") + @Log(title = "客群管理-查询客群详情") @GetMapping("/{id}") public AjaxResult getCustGroup(@PathVariable Long id) { - CustGroupVO vo = custGroupService.getCustGroup(id); - return AjaxResult.success(vo); + CustGroupVO custGroup = custGroupService.getCustGroup(id); + return AjaxResult.success(custGroup); } /** @@ -83,17 +83,6 @@ public class CustGroupController extends BaseController { return AjaxResult.success(custGroupService.createCustGroupByTemplate(custGroup, file)); } - /** - * 更新客群 - */ - @ApiOperation("更新客群") - @Log(title = "客群管理-更新客群", businessType = BusinessType.UPDATE) - @PostMapping("/update") - public AjaxResult updateCustGroup(@RequestBody @Valid CustGroup custGroup) { - String result = custGroupService.updateCustGroup(custGroup); - return AjaxResult.success(result); - } - /** * 更新客群(网格导入) */ @@ -150,15 +139,4 @@ public class CustGroupController extends BaseController { return AjaxResult.success(result); } - /** - * 手动移除客群客户 - */ - @ApiOperation("手动移除客群客户") - @Log(title = "客群管理-手动移除客户", businessType = BusinessType.DELETE) - @PostMapping("/removeMembers") - public AjaxResult removeMembers(@RequestParam Long groupId, @RequestBody List memberIds) { - String result = custGroupService.removeMembers(groupId, memberIds); - return AjaxResult.success(result); - } - } \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/controller/CustGroupMemberController.java b/ibs-group/src/main/java/com/ruoyi/group/controller/CustGroupMemberController.java new file mode 100644 index 0000000..a27ce83 --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/controller/CustGroupMemberController.java @@ -0,0 +1,54 @@ +package com.ruoyi.group.controller; + +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.group.domain.dto.CustGroupMemberQueryDTO; +import com.ruoyi.group.domain.vo.CustGroupMemberVO; +import com.ruoyi.group.service.ICustGroupMemberService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.web.bind.annotation.*; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 客群客户Controller + * + * @author ruoyi + */ +@Api(tags = "客群客户接口") +@RestController +@RequestMapping("/group/member") +public class CustGroupMemberController extends BaseController { + + @Resource + private ICustGroupMemberService custGroupMemberService; + + /** + * 分页查询客群客户列表 + */ + @ApiOperation("分页查询客群客户列表") + @Log(title = "客群客户-查询客户列表") + @GetMapping("/list/{groupId}") + public TableDataInfo listCustGroupMembers(@PathVariable Long groupId, + CustGroupMemberQueryDTO dto) { + startPage(); + List list = custGroupMemberService.listCustGroupMembers(groupId, dto); + return getDataTable(list); + } + + /** + * 手动移除客群客户 + */ + @ApiOperation("手动移除客群客户") + @Log(title = "客群客户-手动移除客户", businessType = BusinessType.DELETE) + @PostMapping("/remove") + public AjaxResult removeMembers(@RequestParam Long groupId, @RequestBody List memberIds) { + String result = custGroupMemberService.removeMembers(groupId, memberIds); + return AjaxResult.success(result); + } +} diff --git a/ibs-group/src/main/java/com/ruoyi/group/domain/dto/CustGroupMemberQueryDTO.java b/ibs-group/src/main/java/com/ruoyi/group/domain/dto/CustGroupMemberQueryDTO.java new file mode 100644 index 0000000..703fc97 --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/domain/dto/CustGroupMemberQueryDTO.java @@ -0,0 +1,27 @@ +package com.ruoyi.group.domain.dto; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +/** + * 客群客户查询DTO + * + * @author ruoyi + */ +@Data +@ApiModel(description = "客群客户查询条件") +public class CustGroupMemberQueryDTO { + + /** + * 客户类型:0=个人, 1=商户, 2=企业 + */ + @ApiModelProperty(value = "客户类型") + private String custType; + + /** + * 客户姓名 + */ + @ApiModelProperty(value = "客户姓名") + private String custName; +} diff --git a/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroup.java b/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroup.java index c8a9a32..106c839 100644 --- a/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroup.java +++ b/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroup.java @@ -59,7 +59,6 @@ public class CustGroup { * 所属机构ID */ @ApiModelProperty(value = "所属机构ID", name = "deptId") - @TableField(fill = FieldFill.INSERT) private Long deptId; /** @@ -74,13 +73,6 @@ public class CustGroup { @ApiModelProperty(value = "可见部门ID列表(逗号分隔)", name = "shareDeptIds") private String shareDeptIds; - /** - * 共享部门ID列表(非表字段,用于接收前端传参) - */ - @ApiModelProperty(value = "共享部门ID列表", name = "shareDeptIdList") - @TableField(exist = false) - private List shareDeptIdList; - /** * 客群状态:0=正常, 1=已禁用 */ @@ -109,14 +101,12 @@ public class CustGroup { /** * 更新者 */ - @TableField(fill = FieldFill.INSERT_UPDATE) private String updateBy; /** * 更新时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - @TableField(fill = FieldFill.INSERT_UPDATE) private Date updateTime; /** diff --git a/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroupMember.java b/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroupMember.java index 9285dc3..ef0da18 100644 --- a/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroupMember.java +++ b/ibs-group/src/main/java/com/ruoyi/group/domain/entity/CustGroupMember.java @@ -63,14 +63,12 @@ public class CustGroupMember { /** * 创建者 */ - @TableField(fill = FieldFill.INSERT) private String createBy; /** * 创建时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") - @TableField(fill = FieldFill.INSERT) private Date createTime; /** diff --git a/ibs-group/src/main/java/com/ruoyi/group/domain/vo/CustGroupVO.java b/ibs-group/src/main/java/com/ruoyi/group/domain/vo/CustGroupVO.java index ebd491d..e87d1a6 100644 --- a/ibs-group/src/main/java/com/ruoyi/group/domain/vo/CustGroupVO.java +++ b/ibs-group/src/main/java/com/ruoyi/group/domain/vo/CustGroupVO.java @@ -65,10 +65,10 @@ public class CustGroupVO { private Integer shareEnabled; /** - * 可见部门ID列表 + * 可见部门ID列表(逗号分隔) */ - @ApiModelProperty(value = "可见部门ID列表", name = "shareDeptIds") - private List shareDeptIds; + @ApiModelProperty(value = "可见部门ID列表(逗号分隔)", name = "shareDeptIds") + private String shareDeptIds; /** * 客群状态:0=正常, 1=已禁用 @@ -114,6 +114,49 @@ public class CustGroupVO { @ApiModelProperty(value = "备注", name = "remark") private String remark; + /** + * 有效期截止时间 + */ + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty(value = "有效期截止时间", name = "validTime") + private Date validTime; + + /** + * 创建状态:0=创建中, 1=创建成功, 2=创建失败 + */ + @ApiModelProperty(value = "创建状态:0=创建中, 1=创建成功, 2=创建失败", name = "createStatus") + private String createStatus; + + /** + * 网格类型:0=绩效网格, 1=地理网格, 2=绘制网格 + */ + @ApiModelProperty(value = "网格类型", name = "gridType") + private String gridType; + + /** + * 绩效业务类型:retail=零售, corporate=公司 + */ + @ApiModelProperty(value = "绩效业务类型", name = "cmpmBizType") + private String cmpmBizType; + + /** + * 客户经理列表(逗号分隔) + */ + @ApiModelProperty(value = "客户经理列表", name = "gridUserNames") + private String gridUserNames; + + /** + * 地理网格ID列表(逗号分隔) + */ + @ApiModelProperty(value = "地理网格ID列表", name = "regionGridIds") + private String regionGridIds; + + /** + * 绘制网格ID列表(逗号分隔) + */ + @ApiModelProperty(value = "绘制网格ID列表", name = "drawGridIds") + private String drawGridIds; + /** * 客户列表 */ diff --git a/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMapper.java b/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMapper.java index 8a42411..25920af 100644 --- a/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMapper.java +++ b/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMapper.java @@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.group.domain.dto.CustGroupQueryDTO; import com.ruoyi.group.domain.entity.CustGroup; import com.ruoyi.group.domain.vo.CustGroupVO; +import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @@ -13,6 +14,7 @@ import java.util.List; * * @author ruoyi */ +@Mapper public interface CustGroupMapper extends BaseMapper { /** @@ -31,4 +33,12 @@ public interface CustGroupMapper extends BaseMapper { * @return 客群VO列表 */ List selectCustGroupList(@Param("dto") CustGroupQueryDTO dto); + + /** + * 根据ID查询客群详情 + * + * @param id 客群ID + * @return 客群VO + */ + CustGroupVO selectCustGroupById(@Param("id") Long id); } \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMemberMapper.java b/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMemberMapper.java index dfff965..5e35445 100644 --- a/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMemberMapper.java +++ b/ibs-group/src/main/java/com/ruoyi/group/mapper/CustGroupMemberMapper.java @@ -1,21 +1,36 @@ package com.ruoyi.group.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.group.domain.dto.CustGroupMemberQueryDTO; import com.ruoyi.group.domain.entity.CustGroupMember; +import com.ruoyi.group.domain.vo.CustGroupMemberVO; +import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import java.util.List; + /** * 客群客户关联Mapper接口 * * @author ruoyi */ +@Mapper public interface CustGroupMemberMapper extends BaseMapper { /** - * 查询客群客户数量 + * 分页查询客群客户列表 * * @param groupId 客群ID - * @return 数量 + * @param dto 查询条件 + * @return 客户列表 */ - Long countByGroupId(@Param("groupId") Long groupId); + List selectCustGroupMemberList(@Param("groupId") Long groupId, + @Param("dto") CustGroupMemberQueryDTO dto); + + /** + * 批量插入客群客户(INSERT IGNORE,遇到重复键自动跳过) + * + * @param memberList 客户列表 + */ + void batchInsertMembers(@Param("list") List memberList); } \ No newline at end of file diff --git a/ibs-group/src/main/java/com/ruoyi/group/service/ICustGroupMemberService.java b/ibs-group/src/main/java/com/ruoyi/group/service/ICustGroupMemberService.java new file mode 100644 index 0000000..e56b818 --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/service/ICustGroupMemberService.java @@ -0,0 +1,32 @@ +package com.ruoyi.group.service; + +import com.ruoyi.group.domain.dto.CustGroupMemberQueryDTO; +import com.ruoyi.group.domain.vo.CustGroupMemberVO; + +import java.util.List; + +/** + * 客群客户服务接口 + * + * @author ruoyi + */ +public interface ICustGroupMemberService { + + /** + * 分页查询客群客户列表 + * + * @param groupId 客群ID + * @param dto 查询条件 + * @return 客户列表 + */ + List listCustGroupMembers(Long groupId, CustGroupMemberQueryDTO dto); + + /** + * 手动移除客群客户 + * + * @param groupId 客群ID + * @param memberIds 客户ID列表 + * @return 结果 + */ + String removeMembers(Long groupId, List memberIds); +} diff --git a/ibs-group/src/main/java/com/ruoyi/group/service/ICustGroupService.java b/ibs-group/src/main/java/com/ruoyi/group/service/ICustGroupService.java index 1a6debd..9a73df6 100644 --- a/ibs-group/src/main/java/com/ruoyi/group/service/ICustGroupService.java +++ b/ibs-group/src/main/java/com/ruoyi/group/service/ICustGroupService.java @@ -23,6 +23,14 @@ public interface ICustGroupService { */ List listCustGroup(CustGroupQueryDTO dto); + /** + * 根据ID查询客群详情 + * + * @param id 客群ID + * @return 客群VO + */ + CustGroupVO getCustGroup(Long id); + /** * 异步创建客群(模板导入) * @@ -57,14 +65,6 @@ public interface ICustGroupService { */ String updateCustGroupByTemplate(CustGroup custGroup, MultipartFile file); - /** - * 更新客群 - * - * @param custGroup 客群实体 - * @return 结果消息 - */ - String updateCustGroup(CustGroup custGroup); - /** * 删除客群 * @@ -73,14 +73,6 @@ public interface ICustGroupService { */ String deleteCustGroup(List idList); - /** - * 获取客群详情 - * - * @param id 客群ID - * @return 客群VO - */ - CustGroupVO getCustGroup(Long id); - /** * 检查客群名称是否存在 * @@ -97,15 +89,6 @@ public interface ICustGroupService { */ String getCreateStatus(Long id); - /** - * 手动移除客群客户 - * - * @param groupId 客群ID - * @param memberIds 客群成员ID列表 - * @return 结果消息 - */ - String removeMembers(Long groupId, List memberIds); - /** * 更新动态客群(定时任务调用) * 根据原始导入条件重新查询并更新客户列表 diff --git a/ibs-group/src/main/java/com/ruoyi/group/service/impl/CustGroupMemberServiceImpl.java b/ibs-group/src/main/java/com/ruoyi/group/service/impl/CustGroupMemberServiceImpl.java new file mode 100644 index 0000000..031a700 --- /dev/null +++ b/ibs-group/src/main/java/com/ruoyi/group/service/impl/CustGroupMemberServiceImpl.java @@ -0,0 +1,57 @@ +package com.ruoyi.group.service.impl; + +import com.ruoyi.group.domain.dto.CustGroupMemberQueryDTO; +import com.ruoyi.group.domain.entity.CustGroup; +import com.ruoyi.group.domain.entity.CustGroupMember; +import com.ruoyi.group.domain.vo.CustGroupMemberVO; +import com.ruoyi.group.mapper.CustGroupMapper; +import com.ruoyi.group.mapper.CustGroupMemberMapper; +import com.ruoyi.group.service.ICustGroupMemberService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; +import java.util.List; + +/** + * 客群客户服务实现类 + * + * @author ruoyi + */ +@Service +public class CustGroupMemberServiceImpl implements ICustGroupMemberService { + + @Resource + private CustGroupMemberMapper custGroupMemberMapper; + + @Resource + private CustGroupMapper custGroupMapper; + + @Override + public List listCustGroupMembers(Long groupId, CustGroupMemberQueryDTO dto) { + return custGroupMemberMapper.selectCustGroupMemberList(groupId, dto); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String removeMembers(Long groupId, List memberIds) { + // 检查客群是否存在 + CustGroup custGroup = custGroupMapper.selectById(groupId); + if (custGroup == null) { + return "客群不存在"; + } + + // 删除客户关联 + memberIds.forEach(memberId -> { + CustGroupMember member = custGroupMemberMapper.selectById(memberId); + if (member != null && member.getGroupId().equals(groupId)) { + // 设置手动移除标识 + member.setManualRemove(1); + // 逻辑删除 + custGroupMemberMapper.deleteById(memberId); + } + }); + + return "移除成功"; + } +} diff --git a/ibs-group/src/main/java/com/ruoyi/group/service/impl/CustGroupServiceImpl.java b/ibs-group/src/main/java/com/ruoyi/group/service/impl/CustGroupServiceImpl.java index aa4df13..a0e8804 100644 --- a/ibs-group/src/main/java/com/ruoyi/group/service/impl/CustGroupServiceImpl.java +++ b/ibs-group/src/main/java/com/ruoyi/group/service/impl/CustGroupServiceImpl.java @@ -2,36 +2,28 @@ package com.ruoyi.group.service.impl; import com.alibaba.excel.EasyExcel; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.ruoyi.common.core.domain.entity.SysDept; import com.ruoyi.common.core.domain.entity.SysUser; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.system.mapper.SysDeptMapper; -import com.ruoyi.ibs.cmpm.domain.entity.GridCmpm; -import com.ruoyi.ibs.cmpm.mapper.GridCmpmMapper; -import com.ruoyi.ibs.draw.domain.entity.DrawGridShapeRelate; -import com.ruoyi.ibs.draw.domain.entity.DrawShapeCust; -import com.ruoyi.ibs.draw.mapper.DrawGridShapeRelateMapper; -import com.ruoyi.ibs.draw.mapper.DrawShapeCustMapper; -import com.ruoyi.ibs.grid.domain.vo.CustVO; +import com.ruoyi.ibs.cmpm.domain.vo.GridCmpmVO; +import com.ruoyi.ibs.cmpm.service.GridCmpmService; +import com.ruoyi.ibs.draw.mapper.DrawGridCustUserUnbindMapper; import com.ruoyi.ibs.grid.service.RegionGridListService; import com.ruoyi.group.domain.dto.CustGroupMemberTemplate; import com.ruoyi.group.domain.dto.CustGroupQueryDTO; import com.ruoyi.group.domain.dto.GridImportDTO; import com.ruoyi.group.domain.entity.CustGroup; import com.ruoyi.group.domain.entity.CustGroupMember; -import com.ruoyi.group.domain.vo.CustGroupMemberVO; import com.ruoyi.group.domain.vo.CustGroupVO; import com.ruoyi.group.mapper.CustGroupMapper; import com.ruoyi.group.mapper.CustGroupMemberMapper; import com.ruoyi.group.service.ICustGroupService; import com.ruoyi.ibs.grid.domain.entity.RegionCustUser; -import com.ruoyi.ibs.grid.domain.entity.RegionGrid; -import com.ruoyi.ibs.grid.mapper.RegionCustUserMapper; -import com.ruoyi.ibs.grid.mapper.RegionGridMapper; +import com.ruoyi.ibs.handler.DynamicTableNameHelper; import lombok.extern.slf4j.Slf4j; -import lombok.val; +import org.springframework.transaction.support.TransactionTemplate; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -61,31 +53,31 @@ public class CustGroupServiceImpl implements ICustGroupService { private ExecutorService executorService; @Resource - private GridCmpmMapper gridCmpmMapper; - - @Resource - private RegionCustUserMapper regionCustUserMapper; - - @Resource - private RegionGridMapper regionGridMapper; + private GridCmpmService gridCmpmService; @Resource private RegionGridListService regionGridListService; @Resource - private DrawShapeCustMapper drawShapeCustMapper; + private DrawGridCustUserUnbindMapper drawGridCustUserUnbindMapper; @Resource - private DrawGridShapeRelateMapper drawGridShapeRelateMapper; - - @Resource - private SysDeptMapper sysDeptMapper; + private TransactionTemplate transactionTemplate; @Override public List listCustGroup(CustGroupQueryDTO dto) { return custGroupMapper.selectCustGroupList(dto); } + @Override + public CustGroupVO getCustGroup(Long id) { + CustGroupVO custGroup = custGroupMapper.selectCustGroupById(id); + if (custGroup == null) { + throw new ServiceException("客群不存在"); + } + return custGroup; + } + @Override @Transactional(rollbackFor = Exception.class) public String createCustGroupByTemplate(CustGroup custGroup, MultipartFile file) { @@ -176,21 +168,6 @@ public class CustGroupServiceImpl implements ICustGroupService { return String.valueOf(custGroup.getId()); } - @Override - @Transactional(rollbackFor = Exception.class) - public String updateCustGroup(CustGroup custGroup) { - CustGroup existGroup = custGroupMapper.selectById(custGroup.getId()); - if (existGroup == null) { - throw new ServiceException("客群不存在"); - } - // 检查客群名称是否存在(排除自己) - if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) { - throw new ServiceException("客群名称已存在"); - } - custGroupMapper.updateById(custGroup); - return "客群更新成功"; - } - @Override @Transactional(rollbackFor = Exception.class) public String updateCustGroupByGrid(GridImportDTO gridImportDTO) { @@ -200,10 +177,17 @@ public class CustGroupServiceImpl implements ICustGroupService { if (existGroup == null) { throw new ServiceException("客群不存在"); } + // 检查客群是否正在创建或更新 + if ("0".equals(existGroup.getCreateStatus())) { + throw new ServiceException("客群正在处理中,请稍后再试"); + } // 更新客群基本信息 if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) { throw new ServiceException("客群名称已存在"); } + // 检查网格条件是否发生变化 + boolean gridConditionChanged = isGridConditionChanged(existGroup, gridImportDTO); + // 重新查询数据库,获取最新状态 CustGroup latestGroup = custGroupMapper.selectById(custGroup.getId()); latestGroup.setGroupName(custGroup.getGroupName()); @@ -212,7 +196,48 @@ public class CustGroupServiceImpl implements ICustGroupService { latestGroup.setValidTime(custGroup.getValidTime()); latestGroup.setShareEnabled(custGroup.getShareEnabled()); latestGroup.setShareDeptIds(custGroup.getShareDeptIds()); + + // 保存新的网格导入条件 + String gridType = gridImportDTO.getGridType(); + latestGroup.setGridType(gridType); + if ("0".equals(gridType)) { + latestGroup.setCmpmBizType(gridImportDTO.getCmpmBizType()); + if (gridImportDTO.getUserNames() != null && !gridImportDTO.getUserNames().isEmpty()) { + latestGroup.setGridUserNames(String.join(",", gridImportDTO.getUserNames())); + } else { + latestGroup.setGridUserNames(null); + } + } else if ("1".equals(gridType)) { + if (gridImportDTO.getRegionGridIds() != null && !gridImportDTO.getRegionGridIds().isEmpty()) { + latestGroup.setRegionGridIds(gridImportDTO.getRegionGridIds().stream() + .map(String::valueOf).collect(Collectors.joining(","))); + } else { + latestGroup.setRegionGridIds(null); + } + } else if ("2".equals(gridType)) { + if (gridImportDTO.getDrawGridIds() != null && !gridImportDTO.getDrawGridIds().isEmpty()) { + latestGroup.setDrawGridIds(gridImportDTO.getDrawGridIds().stream() + .map(String::valueOf).collect(Collectors.joining(","))); + } else { + latestGroup.setDrawGridIds(null); + } + } + // 更新数据库 + latestGroup.setUpdateBy(SecurityUtils.getUsername()); + latestGroup.setUpdateTime(new Date()); + custGroupMapper.updateById(latestGroup); + + // 如果网格条件没有变化,直接返回成功 + if (!gridConditionChanged) { + log.info("客群网格条件未变化,跳过客户重新导入,客群ID:{}", custGroup.getId()); + return "客群更新成功(客户列表无需变更)"; + } + + // 网格条件发生变化,需要重新导入客户 + log.info("客群网格条件已变化,开始重新导入客户,客群ID:{}", custGroup.getId()); + // 设置更新状态为"更新中" + latestGroup.setCreateStatus("0"); custGroupMapper.updateById(latestGroup); // 重新设置回DTO(确保异步线程能获取到正确的ID和状态) gridImportDTO.setCustGroup(latestGroup); @@ -223,6 +248,80 @@ public class CustGroupServiceImpl implements ICustGroupService { return "客群更新中"; } + /** + * 检查网格条件是否发生变化 + */ + private boolean isGridConditionChanged(CustGroup existGroup, GridImportDTO gridImportDTO) { + // 首先检查网格类型是否变化 + String newGridType = gridImportDTO.getGridType(); + String oldGridType = existGroup.getGridType(); + + // 如果旧记录没有网格类型信息,说明需要导入 + if (oldGridType == null) { + return true; + } + + // 网格类型变化 + if (!newGridType.equals(oldGridType)) { + log.info("网格类型变化:旧={}, 新={}", oldGridType, newGridType); + return true; + } + + // 根据网格类型检查具体条件 + if ("0".equals(newGridType)) { + // 绩效网格:比较业务类型和客户经理列表 + String oldBizType = existGroup.getCmpmBizType(); + String newBizType = gridImportDTO.getCmpmBizType(); + List newUserNames = gridImportDTO.getUserNames(); + + if (!Objects.equals(oldBizType, newBizType)) { + log.info("绩效业务类型变化:旧={}, 新={}", oldBizType, newBizType); + return true; + } + + // 比较客户经理列表 + String oldUserNames = existGroup.getGridUserNames(); + String newUserNamesStr = newUserNames == null || newUserNames.isEmpty() + ? null : String.join(",", newUserNames); + if (!Objects.equals(oldUserNames, newUserNamesStr)) { + log.info("客户经理列表变化:旧={}, 新={}", oldUserNames, newUserNamesStr); + return true; + } + } else if ("1".equals(newGridType)) { + // 地理网格:比较网格ID列表 + String oldRegionIds = existGroup.getRegionGridIds(); + List newRegionIds = gridImportDTO.getRegionGridIds(); + String newRegionIdsStr = newRegionIds == null || newRegionIds.isEmpty() + ? null : newRegionIds.stream().map(String::valueOf).sorted().collect(Collectors.joining(",")); + + // 归一化比较(排序后比较) + String oldRegionIdsSorted = oldRegionIds == null ? null : + Arrays.stream(oldRegionIds.split(",")).sorted().collect(Collectors.joining(",")); + + if (!Objects.equals(oldRegionIdsSorted, newRegionIdsStr)) { + log.info("地理网格ID列表变化:旧={}, 新={}", oldRegionIdsSorted, newRegionIdsStr); + return true; + } + } else if ("2".equals(newGridType)) { + // 绘制网格:比较网格ID列表 + String oldDrawIds = existGroup.getDrawGridIds(); + List newDrawIds = gridImportDTO.getDrawGridIds(); + String newDrawIdsStr = newDrawIds == null || newDrawIds.isEmpty() + ? null : newDrawIds.stream().map(String::valueOf).sorted().collect(Collectors.joining(",")); + + // 归一化比较(排序后比较) + String oldDrawIdsSorted = oldDrawIds == null ? null : + Arrays.stream(oldDrawIds.split(",")).sorted().collect(Collectors.joining(",")); + + if (!Objects.equals(oldDrawIdsSorted, newDrawIdsStr)) { + log.info("绘制网格ID列表变化:旧={}, 新={}", oldDrawIdsSorted, newDrawIdsStr); + return true; + } + } + + return false; + } + @Override @Transactional(rollbackFor = Exception.class) public String updateCustGroupByTemplate(CustGroup custGroup, MultipartFile file) { @@ -231,6 +330,10 @@ public class CustGroupServiceImpl implements ICustGroupService { if (existGroup == null) { throw new ServiceException("客群不存在"); } + // 检查客群是否正在创建或更新 + if ("0".equals(existGroup.getCreateStatus())) { + throw new ServiceException("客群正在处理中,请稍后再试"); + } // 更新客群基本信息 if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) { throw new ServiceException("客群名称已存在"); @@ -243,7 +346,11 @@ public class CustGroupServiceImpl implements ICustGroupService { latestGroup.setValidTime(custGroup.getValidTime()); latestGroup.setShareEnabled(custGroup.getShareEnabled()); latestGroup.setShareDeptIds(custGroup.getShareDeptIds()); + // 设置更新状态为"更新中" + latestGroup.setCreateStatus("0"); // 更新数据库 + latestGroup.setUpdateBy(SecurityUtils.getUsername()); + latestGroup.setUpdateTime(new Date()); custGroupMapper.updateById(latestGroup); // 获取当前用户部门编码(异步线程中无法获取) String headId = SecurityUtils.getHeadId(); @@ -269,82 +376,6 @@ public class CustGroupServiceImpl implements ICustGroupService { return "客群删除成功"; } - @Override - @Transactional(rollbackFor = Exception.class) - public String removeMembers(Long groupId, List memberIds) { - if (memberIds == null || memberIds.isEmpty()) { - throw new ServiceException("请选择要移除的客户"); - } - // 检查客群是否存在 - CustGroup custGroup = custGroupMapper.selectById(groupId); - if (custGroup == null) { - throw new ServiceException("客群不存在"); - } - // 标记为手动移除(软删除) - for (Long memberId : memberIds) { - CustGroupMember member = custGroupMemberMapper.selectById(memberId); - if (member != null && member.getGroupId().equals(groupId)) { - member.setManualRemove(1); - member.setDelFlag(1); - custGroupMemberMapper.updateById(member); - } - } - return "成功移除 " + memberIds.size() + " 个客户"; - } - - @Override - public CustGroupVO getCustGroup(Long id) { - CustGroup custGroup = custGroupMapper.selectById(id); - if (custGroup == null) { - throw new ServiceException("客群不存在"); - } - CustGroupVO vo = new CustGroupVO(); - vo.setId(custGroup.getId()); - vo.setGroupName(custGroup.getGroupName()); - vo.setGroupMode(custGroup.getGroupMode()); - vo.setCreateMode(custGroup.getCreateMode()); - vo.setUserName(custGroup.getUserName()); - vo.setNickName(custGroup.getNickName()); - vo.setDeptId(custGroup.getDeptId()); - vo.setShareEnabled(custGroup.getShareEnabled()); - vo.setGroupStatus(custGroup.getGroupStatus()); - vo.setRemark(custGroup.getRemark()); - vo.setCreateBy(custGroup.getCreateBy()); - vo.setCreateTime(custGroup.getCreateTime()); - vo.setUpdateBy(custGroup.getUpdateBy()); - vo.setUpdateTime(custGroup.getUpdateTime()); - // 处理共享部门ID列表 - if (StringUtils.isNotEmpty(custGroup.getShareDeptIds())) { - List deptIds = new ArrayList<>(); - for (String idStr : custGroup.getShareDeptIds().split(",")) { - if (StringUtils.isNotEmpty(idStr)) { - deptIds.add(Long.valueOf(idStr)); - } - } - vo.setShareDeptIds(deptIds); - } - // 查询客户列表 - LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); - wrapper.eq(CustGroupMember::getGroupId, id); - List memberList = custGroupMemberMapper.selectList(wrapper); - List memberVOList = new ArrayList<>(); - for (CustGroupMember member : memberList) { - CustGroupMemberVO memberVO = new CustGroupMemberVO(); - memberVO.setId(member.getId()); - memberVO.setGroupId(member.getGroupId()); - memberVO.setCustType(member.getCustType()); - memberVO.setCustId(member.getCustId()); - memberVO.setCustName(member.getCustName()); - memberVO.setCustIdc(member.getCustIdc()); - memberVO.setSocialCreditCode(member.getSocialCreditCode()); - memberVO.setCreateTime(member.getCreateTime()); - memberVOList.add(memberVO); - } - vo.setCustList(memberVOList); - vo.setCustCount(memberVOList.size()); - return vo; - } - @Override public boolean checkGroupNameExist(String groupName) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); @@ -486,14 +517,14 @@ public class CustGroupServiceImpl implements ICustGroupService { gridImportDTO.setRegionGridIds(Arrays.stream(custGroup.getRegionGridIds().split(",")) .map(Long::valueOf).collect(Collectors.toList())); } - newMemberList.addAll(importFromRegionGrid(custGroup, gridImportDTO)); + newMemberList.addAll(importFromRegionGrid(custGroup, gridImportDTO, headId)); } else if ("2".equals(gridType)) { // 绘制网格 if (StringUtils.isNotEmpty(custGroup.getDrawGridIds())) { gridImportDTO.setDrawGridIds(Arrays.stream(custGroup.getDrawGridIds().split(",")) .map(Long::valueOf).collect(Collectors.toList())); } - newMemberList.addAll(importFromDrawGrid(custGroup, gridImportDTO)); + newMemberList.addAll(importFromDrawGrid(custGroup, gridImportDTO, headId)); } // 计算差异 @@ -601,51 +632,71 @@ public class CustGroupServiceImpl implements ICustGroupService { } memberList.add(member); } - // 批量插入 - int batchSize = 1000; - int successCount = 0; - int skippedCount = 0; - int restoredCount = 0; - for (int i = 0; i < memberList.size(); i += batchSize) { - int endIndex = Math.min(i + batchSize, memberList.size()); - List batchList = memberList.subList(i, endIndex); - for (CustGroupMember member : batchList) { - try { - custGroupMemberMapper.insert(member); - successCount++; - } catch (DuplicateKeyException e) { - // 客户已存在,检查是否是被手动移除的 - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(CustGroupMember::getGroupId, member.getGroupId()) - .eq(CustGroupMember::getCustId, member.getCustId()) - .eq(CustGroupMember::getCustType, member.getCustType()); - CustGroupMember existMember = custGroupMemberMapper.selectOne(queryWrapper); - if (existMember != null && existMember.getManualRemove() != null && existMember.getManualRemove() == 1) { - // 是被手动移除的客户,清除标记并恢复 - existMember.setManualRemove(0); - existMember.setDelFlag(0); - existMember.setCustName(member.getCustName()); - custGroupMemberMapper.updateById(existMember); - restoredCount++; - log.debug("恢复手动移除的客户:groupId={}, custId={}", member.getGroupId(), member.getCustId()); - } else { - // 正常存在的客户,跳过 - skippedCount++; - log.debug("客户已存在,跳过:groupId={}, custId={}", member.getGroupId(), member.getCustId()); + + // 使用编程式事务:先删除旧客户,再插入新客户 + transactionTemplate.executeWithoutResult(status -> { + // 删除该客群的所有旧客户 + log.info("开始删除客群旧客户(模板导入),客群ID:{}", custGroup.getId()); + LambdaQueryWrapper memberWrapper = new LambdaQueryWrapper<>(); + memberWrapper.eq(CustGroupMember::getGroupId, custGroup.getId()); + custGroupMemberMapper.delete(memberWrapper); + log.info("客群旧客户删除完成(模板导入),客群ID:{}", custGroup.getId()); + + // 批量插入新客户 + log.info("开始批量插入客户(模板导入),客群ID:{},客户总数:{}", custGroup.getId(), memberList.size()); + int batchSize = 1000; + int successCount = 0; + int skippedCount = 0; + int restoredCount = 0; + for (int i = 0; i < memberList.size(); i += batchSize) { + int endIndex = Math.min(i + batchSize, memberList.size()); + List batchList = memberList.subList(i, endIndex); + log.info("处理批次 [{}/{}],本批大小:{}", i / batchSize + 1, (memberList.size() + batchSize - 1) / batchSize, batchList.size()); + for (CustGroupMember member : batchList) { + try { + custGroupMemberMapper.insert(member); + successCount++; + } catch (DuplicateKeyException e) { + // 客户已存在,检查是否是被手动移除的 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(CustGroupMember::getGroupId, member.getGroupId()) + .eq(CustGroupMember::getCustId, member.getCustId()) + .eq(CustGroupMember::getCustType, member.getCustType()); + CustGroupMember existMember = custGroupMemberMapper.selectOne(queryWrapper); + if (existMember != null && existMember.getManualRemove() != null && existMember.getManualRemove() == 1) { + // 是被手动移除的客户,清除标记并恢复 + existMember.setManualRemove(0); + existMember.setDelFlag(0); + existMember.setCustName(member.getCustName()); + custGroupMemberMapper.updateById(existMember); + restoredCount++; + log.debug("恢复手动移除的客户:groupId={}, custId={}", member.getGroupId(), member.getCustId()); + } else { + // 正常存在的客户,跳过 + skippedCount++; + log.debug("客户已存在,跳过:groupId={}, custId={}", member.getGroupId(), member.getCustId()); + } } } } - } - log.info("客群客户导入完成(模板),客群ID:{},成功:{},跳过重复:{},恢复:{}", - custGroup.getId(), successCount, skippedCount, restoredCount); - // 更新创建状态为成功 - custGroup.setCreateStatus("1"); - custGroupMapper.updateById(custGroup); + log.info("客群客户导入完成(模板),客群ID:{},成功:{},跳过重复:{},恢复:{}", + custGroup.getId(), successCount, skippedCount, restoredCount); + // 更新创建状态为成功 + custGroup.setCreateStatus("1"); + custGroup.setUpdateBy(custGroup.getCreateBy()); + custGroup.setUpdateTime(new Date()); + custGroupMapper.updateById(custGroup); + }); } catch (Exception e) { + log.error("客群客户导入失败,客群ID:{},异常:{}", custGroup.getId(), e.getMessage(), e); + + // 注意:由于删除和插入在同一事务中,插入失败会自动回滚删除操作,无需手动清理 + // 更新创建状态为失败 custGroup.setCreateStatus("2"); + custGroup.setUpdateBy(custGroup.getCreateBy()); + custGroup.setUpdateTime(new Date()); custGroupMapper.updateById(custGroup); - log.error("客群客户导入失败,客群ID:{}", custGroup.getId(), e); throw new ServiceException("客群客户导入失败: " + e.getMessage()); } } @@ -664,63 +715,122 @@ public class CustGroupServiceImpl implements ICustGroupService { memberList.addAll(importFromCmpmGrid(custGroup, gridImportDTO, headId)); } else if ("1".equals(gridType)) { // 地理网格 - memberList.addAll(importFromRegionGrid(custGroup, gridImportDTO)); + memberList.addAll(importFromRegionGrid(custGroup, gridImportDTO, headId)); } else if ("2".equals(gridType)) { // 绘制网格 - memberList.addAll(importFromDrawGrid(custGroup, gridImportDTO)); + memberList.addAll(importFromDrawGrid(custGroup, gridImportDTO, headId)); } if (memberList.isEmpty()) { throw new ServiceException("未查询到任何客户"); } - // 批量插入 - int batchSize = 1000; - int successCount = 0; - int skippedCount = 0; - int restoredCount = 0; - for (int i = 0; i < memberList.size(); i += batchSize) { - int endIndex = Math.min(i + batchSize, memberList.size()); - List batchList = memberList.subList(i, endIndex); - for (CustGroupMember member : batchList) { - try { - custGroupMemberMapper.insert(member); - successCount++; - } catch (DuplicateKeyException e) { - // 客户已存在,检查是否是被手动移除的 - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - queryWrapper.eq(CustGroupMember::getGroupId, member.getGroupId()) - .eq(CustGroupMember::getCustId, member.getCustId()) - .eq(CustGroupMember::getCustType, member.getCustType()); - CustGroupMember existMember = custGroupMemberMapper.selectOne(queryWrapper); - if (existMember != null && existMember.getManualRemove() != null && existMember.getManualRemove() == 1) { - // 是被手动移除的客户,清除标记并恢复 - existMember.setManualRemove(0); - existMember.setDelFlag(0); - existMember.setCustName(member.getCustName()); - custGroupMemberMapper.updateById(existMember); - restoredCount++; - log.debug("恢复手动移除的客户:groupId={}, custId={}", member.getGroupId(), member.getCustId()); - } else { - // 正常存在的客户,跳过 - skippedCount++; - log.debug("客户已存在,跳过:groupId={}, custId={}", member.getGroupId(), member.getCustId()); + + // 使用编程式事务:先删除旧客户,再插入新客户 + transactionTemplate.executeWithoutResult(status -> { + // 删除该客群的所有旧客户 + log.info("开始删除客群旧客户,客群ID:{}", custGroup.getId()); + LambdaQueryWrapper memberWrapper = new LambdaQueryWrapper<>(); + memberWrapper.eq(CustGroupMember::getGroupId, custGroup.getId()); + custGroupMemberMapper.delete(memberWrapper); + log.info("客群旧客户删除完成,客群ID:{}", custGroup.getId()); + + // 批量插入新客户 + log.info("开始批量插入客户,客群ID:{},客户总数:{}", custGroup.getId(), memberList.size()); + + // 分批批量插入(每批1000条) + int batchSize = 1000; + int totalInserted = 0; + int totalRestored = 0; + int totalSkipped = 0; + + for (int i = 0; i < memberList.size(); i += batchSize) { + int endIndex = Math.min(i + batchSize, memberList.size()); + List batchList = memberList.subList(i, endIndex); + log.info("处理批次 [{}/{}],本批大小:{}", i / batchSize + 1, + (memberList.size() + batchSize - 1) / batchSize, batchList.size()); + + // SQL层面的批量插入 + custGroupMemberMapper.batchInsertMembers(batchList); + + // 查询本批中被手动移除的客户,需要恢复 + List toRestore = findManualRemovedToRestore(custGroup.getId(), batchList); + if (!toRestore.isEmpty()) { + // 恢复被手动移除的客户 + for (CustGroupMember m : toRestore) { + batchList.stream() + .filter(b -> b.getCustId().equals(m.getCustId()) && b.getCustType().equals(m.getCustType())) + .findFirst() + .ifPresent(origin -> m.setCustName(origin.getCustName())); + m.setManualRemove(0); + custGroupMemberMapper.updateById(m); } + log.info("本批恢复被手动移除的客户:{} 条", toRestore.size()); + totalRestored += toRestore.size(); } + + totalInserted += batchList.size() - toRestore.size(); + totalSkipped += toRestore.size(); } - } - log.info("客群客户导入完成(网格),客群ID:{},成功:{},跳过重复:{},恢复:{}", - custGroup.getId(), successCount, skippedCount, restoredCount); - // 更新创建状态为成功 - custGroup.setCreateStatus("1"); - custGroupMapper.updateById(custGroup); + + log.info("客群客户导入完成(网格),客群ID:{},插入:{},恢复:{},跳过:{}", + custGroup.getId(), totalInserted, totalRestored, totalSkipped); + + // 更新创建状态为成功 + custGroup.setCreateStatus("1"); + custGroup.setUpdateBy(custGroup.getCreateBy()); + custGroup.setUpdateTime(new Date()); + log.info("准备更新客群状态为成功,客群ID:{}", custGroup.getId()); + custGroupMapper.updateById(custGroup); + log.info("客群状态更新成功完成,客群ID:{}", custGroup.getId()); + }); } catch (Exception e) { + // 先记录原始异常(必须第一时间记录,避免后续异常覆盖) + log.error("==========客群客户导入异常========== 客群ID:{},异常类型:{},异常消息:{}", + custGroup.getId(), e.getClass().getName(), e.getMessage(), e); + + // 注意:由于删除和插入在同一事务中,插入失败会自动回滚删除操作,无需手动清理 + // 更新创建状态为失败 - custGroup.setCreateStatus("2"); - custGroupMapper.updateById(custGroup); - log.error("客群客户导入失败,客群ID:{}", custGroup.getId(), e); + try { + custGroup.setCreateStatus("2"); + custGroup.setUpdateBy(custGroup.getCreateBy()); + custGroup.setUpdateTime(new Date()); + log.info("准备更新客群状态为失败,客群ID:{}", custGroup.getId()); + custGroupMapper.updateById(custGroup); + log.info("客群状态更新为失败完成,客群ID:{}", custGroup.getId()); + } catch (Exception updateException) { + log.error("==========更新客群状态为失败也异常了========== 客群ID:{},异常类型:{},异常消息:{}", + custGroup.getId(), updateException.getClass().getName(), updateException.getMessage(), updateException); + } throw new ServiceException("客群客户导入失败: " + e.getMessage()); } } + /** + * 查找需要恢复的被手动移除的客户 + * + * @param groupId 客群ID + * @param batchList 本批导入的客户列表 + * @return 需要恢复的客户列表 + */ + private List findManualRemovedToRestore(Long groupId, List batchList) { + // 构建本批客户的 (custId, custType) 集合 + Set batchKeys = batchList.stream() + .map(m -> m.getCustId() + "|" + m.getCustType()) + .collect(Collectors.toSet()); + + // 查询该客群所有被手动移除的客户 + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(CustGroupMember::getGroupId, groupId) + .eq(CustGroupMember::getManualRemove, 1) + .eq(CustGroupMember::getDelFlag, 0); + List manualRemovedList = custGroupMemberMapper.selectList(queryWrapper); + + // 筛选出本批中需要恢复的 + return manualRemovedList.stream() + .filter(m -> batchKeys.contains(m.getCustId() + "|" + m.getCustType())) + .collect(Collectors.toList()); + } + /** * 从绩效网格导入客户 */ @@ -735,13 +845,13 @@ public class CustGroupServiceImpl implements ICustGroupService { gridTypes.add("corporate"); gridTypes.add("corporate_account"); } else { - throw new ServiceException("请选择绩效网格业务类型(零售/公司)"); + throw new ServiceException("请选择绩效网格业务类型(零售/对公/对公账户)"); } // 查询客户 for (String userName : gridImportDTO.getUserNames()) { for (String gridType : gridTypes) { - List cmpmList = gridCmpmMapper.getGridCmpmByUserName(userName, headId, gridType); - for (GridCmpm cmpm : cmpmList) { + List cmpmList = gridCmpmService.selectManageListForImport(gridType, userName, headId); + for (GridCmpmVO cmpm : cmpmList) { CustGroupMember member = new CustGroupMember(); member.setGroupId(custGroup.getId()); member.setCustId(cmpm.getCustId()); @@ -760,25 +870,21 @@ public class CustGroupServiceImpl implements ICustGroupService { /** * 从地理网格导入客户 */ - private List importFromRegionGrid(CustGroup custGroup, GridImportDTO gridImportDTO) { + private List importFromRegionGrid(CustGroup custGroup, GridImportDTO gridImportDTO, String headId) { List memberList = new ArrayList<>(); - // 查询地理网格获取编码 if (gridImportDTO.getRegionGridIds() == null || gridImportDTO.getRegionGridIds().isEmpty()) { throw new ServiceException("请选择地理网格"); } - List regionGrids = regionGridMapper.selectBatchIds(gridImportDTO.getRegionGridIds()); - // 使用 selectAllCustFromGrid 方法查询所有客户(不限制客户类型) - for (RegionGrid regionGrid : regionGrids) { - List custUsers = regionGridListService.selectAllCustFromGrid(regionGrid); - for (RegionCustUser custUser : custUsers) { - CustGroupMember member = new CustGroupMember(); - member.setGroupId(custGroup.getId()); - member.setCustId(custUser.getCustId()); - member.setCustName(custUser.getCustName()); - member.setCustType(custUser.getCustType()); - member.setCreateTime(new Date()); - memberList.add(member); - } + // 直接根据网格ID列表批量查询所有客户(SQL中直接拼接headId,绕过MyBatis-Plus拦截器) + List custUsers = regionGridListService.selectAllCustByGridIds(gridImportDTO.getRegionGridIds(), headId); + for (RegionCustUser custUser : custUsers) { + CustGroupMember member = new CustGroupMember(); + member.setGroupId(custGroup.getId()); + member.setCustId(custUser.getCustId()); + member.setCustName(custUser.getCustName()); + member.setCustType(custUser.getCustType()); + member.setCreateTime(new Date()); + memberList.add(member); } return memberList; } @@ -786,27 +892,21 @@ public class CustGroupServiceImpl implements ICustGroupService { /** * 从绘制网格导入客户 */ - private List importFromDrawGrid(CustGroup custGroup, GridImportDTO gridImportDTO) { + private List importFromDrawGrid(CustGroup custGroup, GridImportDTO gridImportDTO, String headId) { List memberList = new ArrayList<>(); if (gridImportDTO.getDrawGridIds() == null || gridImportDTO.getDrawGridIds().isEmpty()) { throw new ServiceException("请选择绘制网格"); } - // 查询绘制网格关联的图形ID + // 使用 selectCustByDrawGridId 方法(直接在SQL中拼接headId,绕过拦截器) for (Long gridId : gridImportDTO.getDrawGridIds()) { - LambdaQueryWrapper relateWrapper = new LambdaQueryWrapper<>(); - relateWrapper.eq(DrawGridShapeRelate::getGridId, gridId); - List relates = drawGridShapeRelateMapper.selectList(relateWrapper); - for (DrawGridShapeRelate relate : relates) { - // 根据图形ID查询客户 - LambdaQueryWrapper custWrapper = new LambdaQueryWrapper<>(); - custWrapper.eq(DrawShapeCust::getShapeId, relate.getShapeId()); - List shapeCusts = drawShapeCustMapper.selectList(custWrapper); - for (DrawShapeCust shapeCust : shapeCusts) { + List custUsers = drawGridCustUserUnbindMapper.selectCustByDrawGridId(gridId, headId); + if (custUsers != null && !custUsers.isEmpty()) { + for (RegionCustUser custUser : custUsers) { CustGroupMember member = new CustGroupMember(); member.setGroupId(custGroup.getId()); - member.setCustId(shapeCust.getCustId()); - member.setCustName(shapeCust.getCustName()); - member.setCustType(shapeCust.getCustType()); + member.setCustId(custUser.getCustId()); + member.setCustName(custUser.getCustName()); + member.setCustType(custUser.getCustType()); member.setCreateTime(new Date()); memberList.add(member); } diff --git a/ibs-group/src/main/resources/mapper/group/CustGroupMapper.xml b/ibs-group/src/main/resources/mapper/group/CustGroupMapper.xml index e85cb46..3b78e5d 100644 --- a/ibs-group/src/main/resources/mapper/group/CustGroupMapper.xml +++ b/ibs-group/src/main/resources/mapper/group/CustGroupMapper.xml @@ -43,4 +43,33 @@ ORDER BY cg.create_time DESC + + diff --git a/ibs-group/src/main/resources/mapper/group/CustGroupMemberMapper.xml b/ibs-group/src/main/resources/mapper/group/CustGroupMemberMapper.xml new file mode 100644 index 0000000..4832aea --- /dev/null +++ b/ibs-group/src/main/resources/mapper/group/CustGroupMemberMapper.xml @@ -0,0 +1,41 @@ + + + + + + + + + INSERT IGNORE INTO ibs_cust_group_member + (group_id, cust_type, cust_id, cust_name, cust_idc, social_credit_code, create_by, create_time, del_flag, manual_remove) + VALUES + + (#{item.groupId}, #{item.custType}, #{item.custId}, #{item.custName}, #{item.custIdc}, #{item.socialCreditCode}, #{item.createBy}, NOW(), '0', '0') + + + + diff --git a/ibs/src/main/java/com/ruoyi/ibs/cmpm/controller/GridCmpmController.java b/ibs/src/main/java/com/ruoyi/ibs/cmpm/controller/GridCmpmController.java index a7b71f7..f766302 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/cmpm/controller/GridCmpmController.java +++ b/ibs/src/main/java/com/ruoyi/ibs/cmpm/controller/GridCmpmController.java @@ -117,4 +117,14 @@ public class GridCmpmController extends BaseController { public AjaxResult selectCustBaseInfoList(@RequestBody CustBaseInfo custBaseInfo) { return AjaxResult.success( gridCmpmCustService.selectCustInfoList (custBaseInfo)) ; } + + /** + * 根据网格类型获取客户经理列表 + */ + @GetMapping("/managerList") + @Log(title = "绩效网格-获取客户经理列表") + @ApiOperation("获取客户经理列表") + public AjaxResult getManagerListByGridType(@RequestParam String gridType) { + return AjaxResult.success(gridCmpmService.getManagerListByGridType(gridType)); + } } diff --git a/ibs/src/main/java/com/ruoyi/ibs/cmpm/mapper/GridCmpmMapper.java b/ibs/src/main/java/com/ruoyi/ibs/cmpm/mapper/GridCmpmMapper.java index 63fb269..67b7253 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/cmpm/mapper/GridCmpmMapper.java +++ b/ibs/src/main/java/com/ruoyi/ibs/cmpm/mapper/GridCmpmMapper.java @@ -8,11 +8,15 @@ import com.ruoyi.ibs.cmpm.domain.entity.GridCmpm; import com.ruoyi.ibs.cmpm.domain.vo.DwbRetailCustLevelManagerDetailVO; import com.ruoyi.ibs.cmpm.domain.vo.DwbRetailResultVO; import com.ruoyi.ibs.cmpm.domain.vo.GridCmpmClaimVO; +import com.ruoyi.ibs.cmpm.domain.vo.GridCmpmVO; import com.ruoyi.ibs.customerselect.domain.CustBaseInfo; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.cursor.Cursor; +import org.apache.ibatis.session.SqlSessionFactory; import java.util.List; +import java.util.Map; /** * @Author 吴凯程 @@ -60,6 +64,32 @@ public interface GridCmpmMapper { List selectManagerList(); + /** + * 根据网格类型和总行ID查询客户经理列表 + * @param gridType 网格类型(零售retail、对公corporate、对公账户corporate_account) + * @param headId 总行ID + * @return 客户经理列表(user_name, nick_name) + */ + List> getManagerListByGridType(@Param("gridType") String gridType, @Param("headId") String headId); + + /** + * 根据网格类型、总行ID和客户经理查询客户列表(分表查询,适用于客群导入) + * @param gridType 网格类型 + * @param userName 客户经理柜员号 + * @param headId 总行ID + * @return 客户列表 + */ + List getCustomerListForImport(@Param("gridType") String gridType, @Param("userName") String userName, @Param("headId") String headId); + + /** + * 根据网格类型、总行ID和客户经理流式查询客户列表(使用Cursor,适用于大数据量场景) + * @param gridType 网格类型 + * @param userName 客户经理柜员号 + * @param headId 总行ID + * @return 客户列表游标 + */ + Cursor getCustomerListForImportCursor(@Param("gridType") String gridType, @Param("userName") String userName, @Param("headId") String headId); + // List selectCustInfoRetailFromGridCmpm(CustBaseInfo custBaseInfo); // diff --git a/ibs/src/main/java/com/ruoyi/ibs/cmpm/service/GridCmpmService.java b/ibs/src/main/java/com/ruoyi/ibs/cmpm/service/GridCmpmService.java index a399978..2ed9af2 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/cmpm/service/GridCmpmService.java +++ b/ibs/src/main/java/com/ruoyi/ibs/cmpm/service/GridCmpmService.java @@ -302,4 +302,26 @@ public class GridCmpmService { return changesMap; } + /** + * 根据网格类型和总行ID查询客户经理列表 + * @param gridType 网格类型(零售retail、对公corporate、对公账户corporate_account) + * @return 客户经理列表 + */ + public List> getManagerListByGridType(String gridType) { + String headId = SecurityUtils.getHeadId(); + return gridCmpmMapper.getManagerListByGridType(gridType, headId); + } + + /** + * 根据参数查询绩效网格客户列表(不依赖SecurityUtils,适用于异步线程) + * 所有参数由调用者传入,不在方法内部获取用户上下文 + * @param gridType 网格类型 + * @param userName 客户经理柜员号 + * @param headId 总行ID + * @return 客户列表 + */ + public List selectManageListForImport(String gridType, String userName, String headId) { + return gridCmpmMapper.getCustomerListForImport(gridType, userName, headId); + } + } diff --git a/ibs/src/main/java/com/ruoyi/ibs/draw/controller/DrawGridController.java b/ibs/src/main/java/com/ruoyi/ibs/draw/controller/DrawGridController.java index a70e14c..2943c7c 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/draw/controller/DrawGridController.java +++ b/ibs/src/main/java/com/ruoyi/ibs/draw/controller/DrawGridController.java @@ -80,6 +80,16 @@ public class DrawGridController extends BaseController { return getDataTable(gridList, page); } + @ApiOperation("获取网格列表(用于客群创建,简化查询)") + @Log(title = "自定义绘制网格--获取网格列表(客群创建用)") + @GetMapping("/simpleList") + public R> getSimpleGridList(DrawGridListDTO drawGridListDTO) { + drawGridListDTO.setUserName(SecurityUtils.getUsername()); + drawGridListDTO.setDeptId(SecurityUtils.getDeptId()); + List gridList = drawGridService.getSimpleGridList(drawGridListDTO); + return R.ok(gridList); + } + @ApiOperation("分页获取网格内客户列表") @Log(title = "自定义绘制网格--分页获取网格内客户列表") @GetMapping("/cust/list") diff --git a/ibs/src/main/java/com/ruoyi/ibs/draw/mapper/DrawGridCustUserUnbindMapper.java b/ibs/src/main/java/com/ruoyi/ibs/draw/mapper/DrawGridCustUserUnbindMapper.java index 18565cb..9dae8c0 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/draw/mapper/DrawGridCustUserUnbindMapper.java +++ b/ibs/src/main/java/com/ruoyi/ibs/draw/mapper/DrawGridCustUserUnbindMapper.java @@ -4,7 +4,9 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.ibs.draw.domain.dto.grid.DrawGridCustListDTO; import com.ruoyi.ibs.draw.domain.entity.DrawGridCustUserUnbind; import com.ruoyi.ibs.draw.domain.vo.DrawGridCustVO; +import com.ruoyi.ibs.grid.domain.entity.RegionCustUser; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import java.util.List; @@ -18,5 +20,13 @@ public interface DrawGridCustUserUnbindMapper extends BaseMapper getCustListByManager(DrawGridCustListDTO drawGridCustListDTO); + /** + * 根据绘制网格ID查询所有客户(用于客群导入,拼接headId绕过拦截器) + * @param gridId 绘制网格ID + * @param headId 总行机构号前三位(用于拼接动态表名) + * @return 客户列表 + */ + List selectCustByDrawGridId(@Param("gridId") Long gridId, @Param("headId") String headId); + } diff --git a/ibs/src/main/java/com/ruoyi/ibs/draw/mapper/DrawGridShapeRelateMapper.java b/ibs/src/main/java/com/ruoyi/ibs/draw/mapper/DrawGridShapeRelateMapper.java index e9718b8..8931b91 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/draw/mapper/DrawGridShapeRelateMapper.java +++ b/ibs/src/main/java/com/ruoyi/ibs/draw/mapper/DrawGridShapeRelateMapper.java @@ -3,6 +3,10 @@ package com.ruoyi.ibs.draw.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.ruoyi.ibs.draw.domain.entity.DrawGridShapeRelate; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; +import java.util.Map; /** * @Author 吴凯程 @@ -10,4 +14,19 @@ import org.apache.ibatis.annotations.Mapper; **/ @Mapper public interface DrawGridShapeRelateMapper extends BaseMapper { + + /** + * 根据网格ID查询关联的图形(用于异步导入客群,原生SQL绕过拦截器) + * @param gridId 网格ID + * @return 图形关联列表 + */ + List selectByGridId(@Param("gridId") Long gridId); + + /** + * 根据网格ID查询所有客户(用于客群导入,直接拼接headId绕过拦截器) + * @param gridId 网格ID + * @param headId 部门代码(用于拼接动态表名) + * @return 客户列表,包含 custId, custName, custType + */ + List> selectCustListByGridId(@Param("gridId") Long gridId, @Param("headId") String headId); } diff --git a/ibs/src/main/java/com/ruoyi/ibs/draw/service/DrawGridService.java b/ibs/src/main/java/com/ruoyi/ibs/draw/service/DrawGridService.java index bf7f02d..0d30d94 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/draw/service/DrawGridService.java +++ b/ibs/src/main/java/com/ruoyi/ibs/draw/service/DrawGridService.java @@ -24,6 +24,13 @@ public interface DrawGridService { List getGridList(DrawGridListDTO drawGridListDTO); + /** + * 获取网格列表(用于客群创建,简化查询不统计客户数量) + * @param drawGridListDTO 查询条件 + * @return 网格列表(不含客户数量) + */ + List getSimpleGridList(DrawGridListDTO drawGridListDTO); + diff --git a/ibs/src/main/java/com/ruoyi/ibs/draw/service/impl/DrawGridServiceImpl.java b/ibs/src/main/java/com/ruoyi/ibs/draw/service/impl/DrawGridServiceImpl.java index f789580..d11bcfd 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/draw/service/impl/DrawGridServiceImpl.java +++ b/ibs/src/main/java/com/ruoyi/ibs/draw/service/impl/DrawGridServiceImpl.java @@ -334,6 +334,15 @@ public class DrawGridServiceImpl implements DrawGridService { return gridList; } + @Override + public List getSimpleGridList(DrawGridListDTO drawGridListDTO) { + if (SecurityUtils.userRole().equals("manager")) { + return drawGridMapper.getGridListByManager(drawGridListDTO); + } else { + return drawGridMapper.getGridList(drawGridListDTO); + } + } + private CustCountDTO getCustCountByGrid(Long gridId) { CustCountDTO custCountDTO = new CustCountDTO(); LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); diff --git a/ibs/src/main/java/com/ruoyi/ibs/grid/controller/RegionGridListController.java b/ibs/src/main/java/com/ruoyi/ibs/grid/controller/RegionGridListController.java index 8eb7a13..b262777 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/grid/controller/RegionGridListController.java +++ b/ibs/src/main/java/com/ruoyi/ibs/grid/controller/RegionGridListController.java @@ -12,6 +12,7 @@ import com.ruoyi.ibs.grid.domain.dto.GridCustListDTO; import com.ruoyi.ibs.grid.domain.dto.RegionGridListDTO; import com.ruoyi.ibs.grid.domain.entity.RegionGrid; import com.ruoyi.ibs.grid.domain.vo.RegionCustUserVO; +import com.ruoyi.ibs.grid.domain.vo.RegionGridGroupVO; import com.ruoyi.ibs.grid.domain.vo.RegionGridListVO; import com.ruoyi.ibs.grid.mapper.RegionGridMapper; import com.ruoyi.ibs.grid.service.RegionGridListService; @@ -108,6 +109,14 @@ public class RegionGridListController extends BaseController { return AjaxResult.success("开始更新行社所有网格"); } - + /** + * 查询地理网格列表(专用于客群创建) + */ + @ApiOperation("查询地理网格列表(专用于客群创建)") + @Log(title = "地理网格-查询地理网格列表(客群创建用)") + @GetMapping("/groupList") + public AjaxResult getRegionGridListForGroup(RegionGridListDTO dto) { + return AjaxResult.success(regionGridListService.getRegionGridListForGroup(dto)); + } } diff --git a/ibs/src/main/java/com/ruoyi/ibs/grid/domain/vo/RegionGridGroupVO.java b/ibs/src/main/java/com/ruoyi/ibs/grid/domain/vo/RegionGridGroupVO.java new file mode 100644 index 0000000..2d40fa9 --- /dev/null +++ b/ibs/src/main/java/com/ruoyi/ibs/grid/domain/vo/RegionGridGroupVO.java @@ -0,0 +1,28 @@ +package com.ruoyi.ibs.grid.domain.vo; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.io.Serializable; + +/** + * 地理网格选择VO - 专用于客群创建时的网格选择 + * 只包含必要字段,提升查询效率 + * + * @author ruoyi + */ +@Data +public class RegionGridGroupVO implements Serializable { + + /** + * 网格主键 + */ + @ApiModelProperty(value = "网格主键") + private Long gridId; + + /** + * 网格名称 + */ + @ApiModelProperty(value = "网格名称") + private String gridName; +} diff --git a/ibs/src/main/java/com/ruoyi/ibs/grid/mapper/RegionCustUserMapper.java b/ibs/src/main/java/com/ruoyi/ibs/grid/mapper/RegionCustUserMapper.java index 33fe464..e5dc8c3 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/grid/mapper/RegionCustUserMapper.java +++ b/ibs/src/main/java/com/ruoyi/ibs/grid/mapper/RegionCustUserMapper.java @@ -40,6 +40,13 @@ public interface RegionCustUserMapper extends BaseMapper { Long countCustInSecGrid(@Param("gridId") Long gridId, @Param("custType") String custType); + /** + * 根据网格ID列表查询所有客户(用于异步导入客群,直接根据gridIds查询无需区分等级) + * @param gridIds 网格ID列表 + * @param headId 总行机构号前三位(用于拼接动态表名) + */ + List selectAllCustByGridIds(@Param("gridIds") List gridIds, @Param("headId") String headId); + diff --git a/ibs/src/main/java/com/ruoyi/ibs/grid/mapper/RegionGridMapper.java b/ibs/src/main/java/com/ruoyi/ibs/grid/mapper/RegionGridMapper.java index 4a80932..ea8ab9f 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/grid/mapper/RegionGridMapper.java +++ b/ibs/src/main/java/com/ruoyi/ibs/grid/mapper/RegionGridMapper.java @@ -5,6 +5,7 @@ import com.ruoyi.ibs.grid.domain.dto.GridCustListDTO; import com.ruoyi.ibs.grid.domain.dto.RegionGridListDTO; import com.ruoyi.ibs.grid.domain.entity.RegionGrid; import com.ruoyi.ibs.grid.domain.vo.RegionGridCustVO; +import com.ruoyi.ibs.grid.domain.vo.RegionGridGroupVO; import com.ruoyi.ibs.grid.domain.vo.RegionGridListVO; import com.ruoyi.ibs.grid.domain.vo.RegionUnbindVo; import org.apache.ibatis.annotations.Param; @@ -69,4 +70,10 @@ public interface RegionGridMapper extends BaseMapper { List getSecGridIdByTopGridId(@Param("gridId") Long gridId); + /** + * 查询地理网格列表(简化版)- 专用于客群创建 + * 只返回gridId和gridName,避免N+1查询问题 + */ + List getRegionGridListForGroup(RegionGridListDTO regionGridListDTO); + } diff --git a/ibs/src/main/java/com/ruoyi/ibs/grid/service/RegionGridListService.java b/ibs/src/main/java/com/ruoyi/ibs/grid/service/RegionGridListService.java index a1dc384..57f3700 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/grid/service/RegionGridListService.java +++ b/ibs/src/main/java/com/ruoyi/ibs/grid/service/RegionGridListService.java @@ -8,6 +8,7 @@ import com.ruoyi.ibs.grid.domain.entity.RegionCustUser; import com.ruoyi.ibs.grid.domain.entity.RegionGrid; import com.ruoyi.ibs.grid.domain.vo.RegionCustUserVO; import com.ruoyi.ibs.grid.domain.vo.RegionGridCustVO; +import com.ruoyi.ibs.grid.domain.vo.RegionGridGroupVO; import com.ruoyi.ibs.grid.domain.vo.RegionGridListVO; import com.ruoyi.ibs.grid.domain.vo.RegionUnbindVo; @@ -36,11 +37,20 @@ public interface RegionGridListService { List getSecGridListByManager(RegionGridListDTO regionGridListDTO); /** - * 查询网格内所有客户(不限制客户类型,用于导入客群) - * @param regionGrid 地理网格对象 - * @return 网格内所有客户列表 + * 根据网格ID列表批量查询所有客户(用于导入客群,优化版) + * @param gridIds 网格ID列表 + * @param headId 总行机构号前三位(用于拼接动态表名) + * @return 客户列表(去重) */ - List selectAllCustFromGrid(RegionGrid regionGrid); + List selectAllCustByGridIds(List gridIds, String headId); + + /** + * 查询地理网格列表(简化版)- 专用于客群创建 + * 只返回gridId和gridName,避免N+1查询问题 + * @param regionGridListDTO 查询条件 + * @return 网格列表(仅包含ID和名称) + */ + List getRegionGridListForGroup(RegionGridListDTO regionGridListDTO); } diff --git a/ibs/src/main/java/com/ruoyi/ibs/grid/service/impl/RegionGridListServiceImpl.java b/ibs/src/main/java/com/ruoyi/ibs/grid/service/impl/RegionGridListServiceImpl.java index 50dba4c..4478371 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/grid/service/impl/RegionGridListServiceImpl.java +++ b/ibs/src/main/java/com/ruoyi/ibs/grid/service/impl/RegionGridListServiceImpl.java @@ -12,6 +12,7 @@ import com.ruoyi.ibs.grid.domain.dto.UnbindDTO; import com.ruoyi.ibs.grid.domain.entity.*; import com.ruoyi.ibs.grid.domain.vo.RegionCustUserVO; import com.ruoyi.ibs.grid.domain.vo.RegionGridCustVO; +import com.ruoyi.ibs.grid.domain.vo.RegionGridGroupVO; import com.ruoyi.ibs.grid.domain.vo.RegionGridListVO; import com.ruoyi.ibs.grid.domain.vo.RegionUnbindVo; import com.ruoyi.ibs.grid.mapper.*; @@ -432,23 +433,33 @@ public class RegionGridListServiceImpl implements RegionGridListService { } /** - * 查询网格内所有客户(不限制客户类型,用于导入客群) - * @param regionGrid 地理网格对象 - * @return 网格内所有客户列表 + * 根据网格ID列表批量查询所有客户(用于导入客群,优化版) + * @param gridIds 网格ID列表 + * @return 客户列表(去重) */ @Override - public List selectAllCustFromGrid(RegionGrid regionGrid) { - LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); - // 根据网格等级判断使用一级还是二级网格ID查询 - if (regionGrid.getGridLevel().equals("1")) { - queryWrapper.eq(RegionCustUser::getTopGridId, regionGrid.getGridId()); - } else if (regionGrid.getGridLevel().equals("2")) { - queryWrapper.eq(RegionCustUser::getSecGridId, regionGrid.getGridId()); - } else { - throw new ServiceException("无效的网格等级: " + regionGrid.getGridLevel()); + public List selectAllCustByGridIds(List gridIds, String headId) { + return regionCustUserMapper.selectAllCustByGridIds(gridIds, headId); + } + + /** + * 查询地理网格列表(简化版)- 专用于客群创建 + * 只返回gridId和gridName,避免N+1查询问题 + * @param regionGridListDTO 查询条件 + * @return 网格列表(仅包含ID和名称) + */ + @Override + public List getRegionGridListForGroup(RegionGridListDTO regionGridListDTO) { + // 设置当前用户部门ID用于权限过滤 + regionGridListDTO.setDeptId(SecurityUtils.getDeptId()); + + // 如果是总行用户,设置归属部室 + if (SecurityUtils.isHead()) { + regionGridListDTO.setOpsDept(SecurityUtils.getOpsDept()); } - // 不限制客户类型,查询所有类型的客户 - return regionCustUserMapper.selectList(queryWrapper); + + // 直接查询,一次SQL完成,无N+1问题 + return regionGridMapper.getRegionGridListForGroup(regionGridListDTO); } } diff --git a/ibs/src/main/java/com/ruoyi/ibs/list/domain/CustInfoBusinessVo.java b/ibs/src/main/java/com/ruoyi/ibs/list/domain/CustInfoBusinessVo.java index de2f50a..ae18118 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/list/domain/CustInfoBusinessVo.java +++ b/ibs/src/main/java/com/ruoyi/ibs/list/domain/CustInfoBusinessVo.java @@ -41,6 +41,14 @@ public class CustInfoBusinessVo { @ApiModelProperty(value = "手动打标") private List tagManual; + /** 九维画像分数 */ + @ApiModelProperty(value = "九维画像分数") + private Ent9vPortraitOrc ent9vPortrait; + + /** 九维画像详细信息 */ + @ApiModelProperty(value = "九维画像详细信息") + private NineVFinalInfoOrc nineVFinalInfo; + public List getTagManual() { return tagManual; } @@ -187,4 +195,20 @@ public class CustInfoBusinessVo { public void setTabEnmuVos(List tabEnmuVos) { this.tabEnmuVos = tabEnmuVos; } + + public Ent9vPortraitOrc getEnt9vPortrait() { + return ent9vPortrait; + } + + public void setEnt9vPortrait(Ent9vPortraitOrc ent9vPortrait) { + this.ent9vPortrait = ent9vPortrait; + } + + public NineVFinalInfoOrc getNineVFinalInfo() { + return nineVFinalInfo; + } + + public void setNineVFinalInfo(NineVFinalInfoOrc nineVFinalInfo) { + this.nineVFinalInfo = nineVFinalInfo; + } } diff --git a/ibs/src/main/java/com/ruoyi/ibs/list/domain/Ent9vPortraitOrc.java b/ibs/src/main/java/com/ruoyi/ibs/list/domain/Ent9vPortraitOrc.java new file mode 100644 index 0000000..dd27e5a --- /dev/null +++ b/ibs/src/main/java/com/ruoyi/ibs/list/domain/Ent9vPortraitOrc.java @@ -0,0 +1,66 @@ +package com.ruoyi.ibs.list.domain; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 企业九维画像分数对象 ent_9v_portrait_orc_825 + * + * @author ruoyi + * @date 2026-03-18 + */ +@Data +@TableName("ent_9v_portrait_orc_825") +public class Ent9vPortraitOrc implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 统一社会信用代码 */ + private String uniscid; + + /** 客户内码 */ + private String cstId; + + /** 机构号 */ + private String orgNo; + + /** score_1合规经营 */ + private BigDecimal score1; + + /** score_2风险准入 */ + private BigDecimal score2; + + /** score_3高管信用评价 */ + private String score3; + + /** score_4股东信用评价 */ + private BigDecimal score4; + + /** score_5社会贡献度 */ + private BigDecimal score5; + + /** score_6稳定经营 */ + private BigDecimal score6; + + /** score_7经营能力 */ + private BigDecimal score7; + + /** score_8偿债能力 */ + private BigDecimal score8; + + /** score_9潜在代偿资源 */ + private BigDecimal score9; + + /** 九维总分 */ + private BigDecimal scoreAll; + + /** 九维总分排名 */ + private BigDecimal scoreAllRank; + + /** 会计日期 */ + private String datDt; +} diff --git a/ibs/src/main/java/com/ruoyi/ibs/list/domain/NineVFinalInfoOrc.java b/ibs/src/main/java/com/ruoyi/ibs/list/domain/NineVFinalInfoOrc.java new file mode 100644 index 0000000..8cb21e6 --- /dev/null +++ b/ibs/src/main/java/com/ruoyi/ibs/list/domain/NineVFinalInfoOrc.java @@ -0,0 +1,226 @@ +package com.ruoyi.ibs.list.domain; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; + +/** + * 企业九维画像详细信息对象 9v_final_info_orc_825 + * + * @author ruoyi + * @date 2026-03-18 + */ +@Data +@TableName("9v_final_info_orc_825") +public class NineVFinalInfoOrc implements Serializable { + + private static final long serialVersionUID = 1L; + + /** 统一社会信用代码 */ + private String uniscid; + + /** 机构号 */ + private String orgNo; + + /** 是否存在经营异常名录信息 */ + private String score11; + + /** 是否存在严重违法失信企业名单信息 */ + private String score12; + + /** 企业环境行为信仰登记是否为"E"或D */ + private String score13; + + /** 是否存在税务重大税收违法黑名单信息 */ + private String score14; + + /** 是否存在拖欠工资黑名单 */ + private String score15; + + /** 是否存在工商吊销企业信息 */ + private String score16; + + /** 是否存在注销企业信息 */ + private String score21; + + /** 是否存在执行案件信息 */ + private String score22; + + /** 是否存在查封信息 */ + private String score23; + + /** 是否存在单位未履行生效裁判信息 */ + private String score24; + + /** 是否存在企业破产清算信息 */ + private String score25; + + /** 是否失信被执行人 */ + private String score26; + + /** 是否为诉讼案件被告 */ + private String score27; + + /** 是否存在查封信息(score_3) */ + private String score31; + + /** 是否存在执行案件信息(score_3) */ + private String score32; + + /** 是否存在个人未履行生效裁判信息 */ + private String score33; + + /** 是否存在拖欠工资黑名单(score_3) */ + private String score34; + + /** 是否失信被执行人(score_3) */ + private String score35; + + /** 是否存在刑事案件被告人生效判决信息 */ + private String score36; + + /** 是否为诉讼案件被告(score_3) */ + private String score37; + + /** 是否存在查封信息(score_4) */ + private String score41; + + /** 是否存在执行案件信息(score_4) */ + private String score42; + + /** 是否存在个人未履行生效裁判信息(score_4) */ + private String score43; + + /** 是否存在拖欠工资黑名单(score_4) */ + private String score44; + + /** 是否失信被执行人(score_4) */ + private String score45; + + /** 是否存在刑事案件被告人生效判决信息(score_4) */ + private String score46; + + /** 是否为诉讼案件被告(score_4) */ + private String score47; + + /** 是否存在企业未履行生效裁判信息 */ + private String score48; + + /** 前12个月纳税金额 */ + private String score51; + + /** 纳税等级 */ + private String score52; + + /** 缴纳社保人数 */ + private String score53; + + /** 公积金缴纳人数 */ + private String score54; + + /** 是否为出口退税生产清单企业 */ + private String score55; + + /** 市场主体经营年限 */ + private String score61; + + /** 股东(或发起人)或投资人信息认缴出资人数 */ + private String score62; + + /** 最大股东持股占比 */ + private String score63; + + /** 近三年法定代表人变更次数 */ + private String score64; + + /** 近三年股东变更次数 */ + private String score65; + + /** 近三年经营范围变更次数 */ + private String score66; + + /** 近三年经营地址变更次数 */ + private String score67; + + /** 近三年缴税年数 */ + private String score68; + + /** 法人户籍 */ + private String score69; + + /** 法人婚姻状况 */ + private String score610; + + /** 上年增值税金额 */ + private String score71; + + /** 今年增值税同比变动 */ + private String score72; + + /** 上年企业所得税金额 */ + private String score73; + + /** 今年所得税同比变动 */ + private String score74; + + /** 缴纳社保人数同比变动 */ + private String score75; + + /** 公积金缴纳人数同比变动 */ + private String score76; + + /** 当年纳税金额同比变动 */ + private String score77; + + /** 上年出口退税金额 */ + private String score78; + + /** 房产套数 */ + private String score81; + + /** 房产面积 */ + private String score82; + + /** 未抵押房产套数 */ + private String score83; + + /** 未抵押房产面积 */ + private String score84; + + /** 已抵押房产被担保债权数额 */ + private String score85; + + /** 企业车产金额 */ + private String score86; + + /** 法人及股东房产面积合计 */ + private String score91; + + /** 法人及股东房产套数合计 */ + private String score92; + + /** 法人及股东未抵押房产套数合计 */ + private String score93; + + /** 法人及股东未抵押房产面积 */ + private String score94; + + /** 法人及股东已抵押房产被担保债权数额合计 */ + private String score95; + + /** 法人代表车产金额 */ + private String score96; + + /** 生态信用扣分 */ + private String scoreB015; + + /** 列入环境失信黑名单时间 */ + private String scoreB016Time; + + /** 列入环境失信黑名单原因 */ + private String scoreB016Reason; + + /** 环境行为信用评价等级 */ + private String scoreA020; +} diff --git a/ibs/src/main/java/com/ruoyi/ibs/list/mapper/Ent9vPortraitOrcMapper.java b/ibs/src/main/java/com/ruoyi/ibs/list/mapper/Ent9vPortraitOrcMapper.java new file mode 100644 index 0000000..13e1182 --- /dev/null +++ b/ibs/src/main/java/com/ruoyi/ibs/list/mapper/Ent9vPortraitOrcMapper.java @@ -0,0 +1,13 @@ +package com.ruoyi.ibs.list.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.ibs.list.domain.Ent9vPortraitOrc; + +/** + * 企业九维画像分数Mapper接口 + * + * @author ruoyi + * @date 2026-03-18 + */ +public interface Ent9vPortraitOrcMapper extends BaseMapper { +} diff --git a/ibs/src/main/java/com/ruoyi/ibs/list/mapper/NineVFinalInfoOrcMapper.java b/ibs/src/main/java/com/ruoyi/ibs/list/mapper/NineVFinalInfoOrcMapper.java new file mode 100644 index 0000000..68e9ba3 --- /dev/null +++ b/ibs/src/main/java/com/ruoyi/ibs/list/mapper/NineVFinalInfoOrcMapper.java @@ -0,0 +1,13 @@ +package com.ruoyi.ibs.list.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ruoyi.ibs.list.domain.NineVFinalInfoOrc; + +/** + * 企业九维画像详细信息Mapper接口 + * + * @author ruoyi + * @date 2026-03-18 + */ +public interface NineVFinalInfoOrcMapper extends BaseMapper { +} diff --git a/ibs/src/main/java/com/ruoyi/ibs/list/service/impl/CustInfoBusinessServiceImpl.java b/ibs/src/main/java/com/ruoyi/ibs/list/service/impl/CustInfoBusinessServiceImpl.java index 9ae2508..c612e22 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/list/service/impl/CustInfoBusinessServiceImpl.java +++ b/ibs/src/main/java/com/ruoyi/ibs/list/service/impl/CustInfoBusinessServiceImpl.java @@ -24,7 +24,9 @@ import com.ruoyi.ibs.grid.util.CustExcelUtil; import com.ruoyi.ibs.list.domain.*; import com.ruoyi.ibs.list.mapper.CorporateShareholderMapper; import com.ruoyi.ibs.list.mapper.CustInfoRetailMapper; +import com.ruoyi.ibs.list.mapper.Ent9vPortraitOrcMapper; import com.ruoyi.ibs.list.mapper.FamilyMembersMapper; +import com.ruoyi.ibs.list.mapper.NineVFinalInfoOrcMapper; import com.ruoyi.ibs.list.mapper.SignedProductsMapper; import com.ruoyi.ibs.list.service.ICustInfoBusinessService; import com.ruoyi.system.mapper.SysIndustryMapper; @@ -75,6 +77,12 @@ public class CustInfoBusinessServiceImpl implements ICustInfoBusinessService @Autowired private FamilyMembersMapper familyMembersMapper; + @Autowired + private Ent9vPortraitOrcMapper ent9vPortraitOrcMapper; + + @Autowired + private NineVFinalInfoOrcMapper nineVFinalInfoOrcMapper; + @Autowired private CustMapMapper custMapMapper; @@ -138,6 +146,17 @@ public class CustInfoBusinessServiceImpl implements ICustInfoBusinessService //查询手动标签列表 List tagCreateVos = familyMembersMapper.selectManualTag(params); custInfoBusinessVo.setTagManual(TreeNode.convertToTreeByParentId(tagCreateVos)); + + // 仅当登录机构为825时,查询九维画像数据 +// if ("825".equals(getHeadId())) { +// LambdaQueryWrapper portraitWrapper = new LambdaQueryWrapper<>(); +// portraitWrapper.eq(Ent9vPortraitOrc::getUniscid, custInfoBusiness.getSocialCreditCode()); +// custInfoBusinessVo.setEnt9vPortrait(ent9vPortraitOrcMapper.selectOne(portraitWrapper)); +// +// LambdaQueryWrapper finalInfoWrapper = new LambdaQueryWrapper<>(); +// finalInfoWrapper.eq(NineVFinalInfoOrc::getUniscid, custInfoBusiness.getSocialCreditCode()); +// custInfoBusinessVo.setNineVFinalInfo(nineVFinalInfoOrcMapper.selectOne(finalInfoWrapper)); +// } } return custInfoBusinessVo; } diff --git a/ibs/src/main/java/com/ruoyi/ibs/task/controller/WorkRecordController.java b/ibs/src/main/java/com/ruoyi/ibs/task/controller/WorkRecordController.java index fa9900a..32d5b66 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/task/controller/WorkRecordController.java +++ b/ibs/src/main/java/com/ruoyi/ibs/task/controller/WorkRecordController.java @@ -131,4 +131,15 @@ public class WorkRecordController extends BaseController { return toAjax(workRecordService.updateReadTime(id)); } + /** + * 查询所有预警类型 + */ + @GetMapping("/alter/types") + @Log(title = "工作台-查询预警类型") + @ApiOperation("查询所有预警类型") + public AjaxResult getAlterTypes() { + List types = workRecordService.getAlterTypes(); + return AjaxResult.success(types); + } + } diff --git a/ibs/src/main/java/com/ruoyi/ibs/task/mapper/WorkRecordMapper.java b/ibs/src/main/java/com/ruoyi/ibs/task/mapper/WorkRecordMapper.java index 07e1e32..8cb355d 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/task/mapper/WorkRecordMapper.java +++ b/ibs/src/main/java/com/ruoyi/ibs/task/mapper/WorkRecordMapper.java @@ -112,4 +112,12 @@ public interface WorkRecordMapper extends BaseMapper { List selectAlterConfigList(String alterType); int updateAlterConfig(AlterConfig alterConfig); + + /** + * 查询所有预警类型 + * + * @param headId 总部ID(部门ID前三位) + * @return 预警类型列表 + */ + List selectAlterTypes(@Param("headId") String headId); } diff --git a/ibs/src/main/java/com/ruoyi/ibs/task/service/WorkRecordService.java b/ibs/src/main/java/com/ruoyi/ibs/task/service/WorkRecordService.java index 6965333..30301ce 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/task/service/WorkRecordService.java +++ b/ibs/src/main/java/com/ruoyi/ibs/task/service/WorkRecordService.java @@ -85,4 +85,11 @@ public interface WorkRecordService{ int updateReadTime(Long id); AlterCountVO getAlterCount(Date reportTime); + + /** + * 查询所有预警类型 + * + * @return 预警类型列表 + */ + List getAlterTypes(); } diff --git a/ibs/src/main/java/com/ruoyi/ibs/task/service/impl/WorkRecordServiceImpl.java b/ibs/src/main/java/com/ruoyi/ibs/task/service/impl/WorkRecordServiceImpl.java index bbae2b6..4970dbd 100644 --- a/ibs/src/main/java/com/ruoyi/ibs/task/service/impl/WorkRecordServiceImpl.java +++ b/ibs/src/main/java/com/ruoyi/ibs/task/service/impl/WorkRecordServiceImpl.java @@ -13,6 +13,7 @@ import java.util.stream.IntStream; import com.github.pagehelper.Page; import com.ruoyi.common.core.page.TableDataPageInfo; +import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.utils.PageUtils; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.ibs.task.domain.dto.WorkRecordDTO; @@ -39,6 +40,8 @@ import org.springframework.transaction.annotation.Transactional; @Service public class WorkRecordServiceImpl implements WorkRecordService { + private final static String alterTypesRedisKey = "work:record:alter:types"; + @Autowired private WorkRecordMapper workRecordMapper; @@ -48,6 +51,9 @@ public class WorkRecordServiceImpl implements WorkRecordService { @Autowired private ISysDeptService sysDeptService; + @Autowired + private RedisCache redisCache; + /** * 查询我的工作清单 * @@ -253,6 +259,19 @@ public class WorkRecordServiceImpl implements WorkRecordService { return alterCountVO; } + @Override + public List getAlterTypes() { + String headId = SecurityUtils.getHeadId(); + String cacheKey = alterTypesRedisKey + ":" + headId; + List cachedTypes = redisCache.getCacheObject(cacheKey); + if (cachedTypes != null) { + return cachedTypes; + } + List types = workRecordMapper.selectAlterTypes(headId); + redisCache.setCacheObjectToEndDay(cacheKey, types); + return types; + } + /** * 获取当前登录用户权限范围内所有post_ids * @return diff --git a/ibs/src/main/resources/mapper/WorkRecordMapper.xml b/ibs/src/main/resources/mapper/WorkRecordMapper.xml index 400c7cd..33a16ab 100644 --- a/ibs/src/main/resources/mapper/WorkRecordMapper.xml +++ b/ibs/src/main/resources/mapper/WorkRecordMapper.xml @@ -193,7 +193,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and status = #{status} - and alter_type LIKE CONCAT('%', #{alterType}, '%') + and alter_type = #{alterType} order by create_time desc, status asc @@ -232,7 +232,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" and wr.status = #{status} - and wr.alter_type LIKE CONCAT('%', #{alterType}, '%') + and wr.alter_type = #{alterType} and ((wr.alter_type = '走访异常提醒' and wr.user_name = #{username}) or @@ -268,4 +268,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" where id = #{id} + + \ No newline at end of file diff --git a/ibs/src/main/resources/mapper/cmpm/GridCmpmMapper.xml b/ibs/src/main/resources/mapper/cmpm/GridCmpmMapper.xml index bc24159..bef24dc 100644 --- a/ibs/src/main/resources/mapper/cmpm/GridCmpmMapper.xml +++ b/ibs/src/main/resources/mapper/cmpm/GridCmpmMapper.xml @@ -505,4 +505,26 @@ where manager_id is not null + + + + + + + + + \ No newline at end of file diff --git a/ibs/src/main/resources/mapper/draw/DrawGridCustMapper.xml b/ibs/src/main/resources/mapper/draw/DrawGridCustMapper.xml index 0f865d4..caa9d07 100644 --- a/ibs/src/main/resources/mapper/draw/DrawGridCustMapper.xml +++ b/ibs/src/main/resources/mapper/draw/DrawGridCustMapper.xml @@ -55,8 +55,17 @@ AND b.cust_name like concat('%', #{custName}, '%') AND b.cust_type = #{custType} - + SELECT DISTINCT + sc.cust_id, + sc.cust_name, + sc.cust_type + FROM grid_draw_shape_relate sr + INNER JOIN draw_shape_cust_${headId} sc ON sc.shape_id = sr.shape_id + WHERE sr.grid_id = #{gridId} + AND sr.delete_flag = '0' \ No newline at end of file diff --git a/ibs/src/main/resources/mapper/grid/RegionGridCustUserMapper.xml b/ibs/src/main/resources/mapper/grid/RegionGridCustUserMapper.xml index 39744fe..86ca7f8 100644 --- a/ibs/src/main/resources/mapper/grid/RegionGridCustUserMapper.xml +++ b/ibs/src/main/resources/mapper/grid/RegionGridCustUserMapper.xml @@ -121,4 +121,30 @@ WHERE sec_grid_id = #{gridId} AND cust_type = #{custType} + + + + + + \ No newline at end of file diff --git a/ibs/src/main/resources/mapper/grid/RegionGridMapper.xml b/ibs/src/main/resources/mapper/grid/RegionGridMapper.xml index c098bf5..8f9da3c 100644 --- a/ibs/src/main/resources/mapper/grid/RegionGridMapper.xml +++ b/ibs/src/main/resources/mapper/grid/RegionGridMapper.xml @@ -339,4 +339,26 @@ + + + \ No newline at end of file diff --git a/ruoyi-ui/CLAUDE.md b/ruoyi-ui/CLAUDE.md new file mode 100644 index 0000000..a408e6c --- /dev/null +++ b/ruoyi-ui/CLAUDE.md @@ -0,0 +1,168 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## 项目概述 + +**数字支行辅助管理系统** - 基于 RuoYi-Vue 框架的银行客户网格化管理平台。 + +这是一个全栈项目: +- **前端**: `ruoyi-ui/` - Vue 2.6 + Element UI +- **后端**: `../ibs/` - Java Spring Boot (若依框架) + +## 常用命令 + +### 开发 +```bash +# 安装依赖(建议使用国内镜像) +npm install --registry=https://registry.npmmirror.com + +# 启动开发服务器 (默认端口80) +npm run dev + +# Node.js 版本兼容性问题的启动方式 +npm run dev_t +``` + +### 构建 +```bash +# 构建生产环境 +npm run build:prod + +# 构建测试环境 +npm run build:stage + +# 构建预发布环境 +npm run build:pre +``` + +### 代码质量 +```bash +# ESLint 检查 +npm run lint +``` + +### Mock 服务 +```bash +# 启动 Mock 服务器 +npm run mock +``` + +## 项目架构 + +### 目录结构 + +``` +ruoyi-ui/ +├── src/ +│ ├── api/ # API 接口层,按业务模块分组 +│ ├── assets/ # 静态资源(图片、样式、图标SVG) +│ ├── components/ # 全局通用组件 +│ ├── directive/ # Vue 自定义指令 +│ ├── layout/ # 布局组件(侧边栏、头部等) +│ ├── map/ # 地图相关工具/配置 +│ ├── plugins/ # 插件配置 +│ ├── router/ # 路由配置 +│ ├── store/ # Vuex 状态管理 +│ ├── utils/ # 工具函数 +│ ├── views/ # 页面组件 +│ ├── App.vue # 根组件 +│ ├── main.js # 入口文件 +│ └── permission.js # 路由权限控制 +├── public/ +│ ├── baidu/ # 百度地图静态资源 +│ └── index.html +├── mock/ # Mock 数据 +├── .env.development # 开发环境配置 +├── .env.production # 生产环境配置 +└── .env.staging # 测试环境配置 +``` + +### 核心业务模块 + +| 目录 | 功能 | +|------|------| +| `views/grid/` | 网格管理(创建、列表、地图、走访、绩效) | +| `views/customer/` | 客户管理(360视图、建档、客群、标签) | +| `views/system/` | 系统管理(用户、角色、菜单、字典) | +| `views/monitor/` | 系统监控(日志、在线用户、服务监控) | +| `views/configure/` | 配置管理(级别配置、参数设置、模板) | +| `views/approveCenter/` | 审批中心 | +| `views/gridSearch/` | 网格搜索(管户、绩效、公海池) | + +### API 层组织 + +API 请求按业务模块组织在 `src/api/` 目录: +- 每个模块导出具体的请求函数 +- 使用 `src/utils/request.js` 中的 axios 实例 +- 默认添加 Bearer Token 认证 +- 统一错误处理(401/500/601等状态码) + +### 状态管理 (Vuex) + +Store 模块位于 `src/store/modules/`: +- `app` - 应用状态(侧边栏、设备类型) +- `user` - 用户信息 +- `permission` - 权限路由 +- `settings` - 系统设置 +- `dict` - 字典数据 +- `tagsView` - 标签页视图 +- `featuredAreas` - 特色区域 +- `rate` - 汇率相关 + +使用 `vuex-persistedstate` 将 settings 持久化到 localStorage。 + +### 路由系统 + +- 路由分为 `constantRoutes`(静态路由)和 `dynamicRoutes`(动态路由) +- 动态路由基于用户权限(permissions)动态加载 +- 使用 `src/permission.js` 进行路由守卫和权限控制 +- 支持嵌套路由和路由元信息配置 + +### 百度地图集成 + +项目深度集成百度地图 API: +- 静态资源位于 `public/baidu/` +- 地图相关 API 在 `src/api/grid/` 下 +- 支持 WebGL 渲染、绘制管理器、热力图等 + +## 环境变量配置 + +| 变量 | 说明 | +|------|------| +| `VUE_APP_BASE_API` | 后端 API 基础路径 | +| `VUE_APP_MOCK_API` | Mock API 路径 | +| `VUE_APP_MOCK` | 是否启用 Mock | +| `VUE_APP_STAGE_URL` | 测试环境地址 | +| `VUE_APP_BAIDU_URL` | 百度地图服务地址 | +| `VUE_APP_SHARP_URL` | SHARP 服务地址 | + +## 代码规范 + +- ESLint 配置基于 `plugin:vue/recommended` +- 使用单引号、2空格缩进 +- 组件名使用 PascalCase +- pre-commit 钩子自动执行 lint-staged + +## 全局组件 + +以下组件已全局注册,可直接使用: +- `Pagination` - 分页组件 +- `RightToolbar` - 表格工具栏 +- `Editor` - 富文本编辑器 +- `FileUpload` - 文件上传 +- `ImageUpload` - 图片上传 +- `ImagePreview` - 图片预览 +- `DictTag` - 字典标签 +- `treeselect` - 树形选择器 +- `DownloadBtn` - 下载按钮 + +## 全局方法 + +通过 `Vue.prototype` 挂载的全局方法: +- `getDicts()` - 获取字典数据 +- `getConfigKey()` - 获取配置项 +- `parseTime()` - 时间格式化 +- `resetForm()` - 重置表单 +- `download()` - 文件下载 +- `handleTree()` - 处理树形数据 diff --git a/ruoyi-ui/src/api/grid/list/gridlist.js b/ruoyi-ui/src/api/grid/list/gridlist.js index 1d4d60e..6f664ed 100644 --- a/ruoyi-ui/src/api/grid/list/gridlist.js +++ b/ruoyi-ui/src/api/grid/list/gridlist.js @@ -287,6 +287,15 @@ export function getFeaturegrid(data) { }) } +// 获取网格列表(用于客群创建,简化查询) +export function getSimpleGridList(data) { + return request({ + url: `/draw/grid/simpleList`, + method: 'get', + params: data + }) +} + // 删除网格 export function featureGridDelete(data) { return request({ diff --git a/ruoyi-ui/src/api/group/custGroup.js b/ruoyi-ui/src/api/group/custGroup.js new file mode 100644 index 0000000..caa4aac --- /dev/null +++ b/ruoyi-ui/src/api/group/custGroup.js @@ -0,0 +1,137 @@ +import request from '@/utils/request' + +// 查询客群列表 +export function listCustGroup(query) { + return request({ + url: '/group/cust/list', + method: 'get', + params: query + }) +} + +// 获取客群详情 +export function getCustGroup(id) { + return request({ + url: '/group/cust/' + id, + method: 'get' + }) +} + +// 异步创建客群(网格导入) +export function createCustGroupByGrid(data) { + return request({ + url: '/group/cust/createByGrid', + method: 'post', + data: data + }) +} + +// 异步创建客群(模板导入) +export function createCustGroupByTemplate(data) { + return request({ + url: '/group/cust/createByTemplate', + method: 'post', + data: data, + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +// 更新客群 +export function updateCustGroup(data) { + return request({ + url: '/group/cust/update', + method: 'post', + data: data + }) +} + +// 更新客群(网格导入) +export function updateCustGroupByGrid(data) { + return request({ + url: '/group/cust/updateByGrid', + method: 'post', + data: data + }) +} + +// 更新客群(模板导入) +export function updateCustGroupByTemplate(data) { + return request({ + url: '/group/cust/updateByTemplate', + method: 'post', + data: data, + headers: { 'Content-Type': 'multipart/form-data' } + }) +} + +// 轮询客群创建状态 +export function getCreateStatus(id) { + return request({ + url: '/group/cust/createStatus/' + id, + method: 'get' + }) +} + +// 客户信息模板下载 +export function downloadTemplate() { + return request({ + url: '/group/cust/download', + method: 'post', + responseType: 'blob' + }) +} + +// 删除客群 +export function deleteCustGroup(idList) { + return request({ + url: '/group/cust/delete', + method: 'post', + data: idList + }) +} + +// 手动移除客群客户 +export function removeMembers(groupId, memberIds) { + return request({ + url: '/group/cust/removeMembers', + method: 'post', + params: { groupId: groupId }, + data: memberIds + }) +} + +// 检查客群名称是否存在 +export function checkGroupNameExist(groupName) { + return request({ + url: '/group/cust/checkName', + method: 'get', + params: { groupName: groupName } + }) +} + +// 根据网格类型获取客户经理列表 +export function getManagerList(gridType) { + return request({ + url: '/grid/cmpm/managerList', + method: 'get', + params: { gridType: gridType } + }) +} + +// 分页查询客群成员列表 +export function listCustGroupMembers(groupId, query) { + return request({ + url: '/group/member/list/' + groupId, + method: 'get', + params: query + }) +} + +// 查询地理网格列表(专用于客群创建) +export function getRegionGridListForGroup(query) { + return request({ + url: '/grid/region/groupList', + method: 'get', + params: query + }) +} diff --git a/ruoyi-ui/src/api/system/home.js b/ruoyi-ui/src/api/system/home.js index 6083a9e..3a13abd 100644 --- a/ruoyi-ui/src/api/system/home.js +++ b/ruoyi-ui/src/api/system/home.js @@ -257,4 +257,12 @@ export function warningCardNum(query) { method: 'get', params: query }) + } + +// 查询所有预警类型 +export function getAlterTypes() { + return request({ + url: '/work/record/alter/types', + method: 'get' + }) } \ No newline at end of file diff --git a/ruoyi-ui/src/router/index.js b/ruoyi-ui/src/router/index.js index 6491862..b8afced 100644 --- a/ruoyi-ui/src/router/index.js +++ b/ruoyi-ui/src/router/index.js @@ -227,6 +227,17 @@ export const constantRoutes = [ }] }, + { + path: '/group/custGroup/detail', + component: Layout, + hidden: true, + children: [{ + path: '/group/custGroup/detail', + name: 'CustGroupDetail', + meta: { title: '客群详情', activeMenu: '/group/custGroup' }, + component: () => import('@/views/group/custGroup/detail') + }] + }, { path: '/checklist/customerlist', component: Layout, diff --git a/ruoyi-ui/src/store/modules/user.js b/ruoyi-ui/src/store/modules/user.js index a0f1f81..8af1f48 100644 --- a/ruoyi-ui/src/store/modules/user.js +++ b/ruoyi-ui/src/store/modules/user.js @@ -11,6 +11,7 @@ const user = { permissions: [], nickName: '', userName: '', + deptId: '', groupErr:{}, }, diff --git a/ruoyi-ui/src/views/grid/charts/customer/index.vue b/ruoyi-ui/src/views/grid/charts/customer/index.vue index 4bdeb85..50a0570 100644 --- a/ruoyi-ui/src/views/grid/charts/customer/index.vue +++ b/ruoyi-ui/src/views/grid/charts/customer/index.vue @@ -2,9 +2,16 @@
- 企业 - 个人 - 商户 + +
@@ -317,6 +324,9 @@ export default { }, computed: { ...mapGetters(['roles', 'userName']), + deptId() { + return this.$store.state.user.deptId + }, filtereDate() { if (this.searchQuery) { return this.tableData.filter((item) => item.companyName.includes(this.searchQuery) || item.legalName.includes(this.searchQuery)) @@ -776,6 +786,12 @@ export default { if (query.backUrl) { this.selectedTab = query.selectedTab this.queryParams.custPattern = query.selectedTab + } else { + // 默认选中第一个tab + const deptId = this.$store.state.user.deptId + const deptIdStr = deptId ? String(deptId) : '' + this.selectedTab = deptIdStr.startsWith('875') ? '0' : '2' + this.queryParams.custPattern = this.selectedTab } }, mounted() { diff --git a/ruoyi-ui/src/views/grid/charts/customerPerformance/constant-info.js b/ruoyi-ui/src/views/grid/charts/customerPerformance/constant-info.js index 9c97c3f..73ded39 100644 --- a/ruoyi-ui/src/views/grid/charts/customerPerformance/constant-info.js +++ b/ruoyi-ui/src/views/grid/charts/customerPerformance/constant-info.js @@ -1,5 +1,22 @@ -const commonCol = function (isCom, type) { - return [ +// 当headId=875时需要隐藏的字段列表 +const HIDDEN_PROPS_FOR_875 = [ + 'regionTopGridName', // 总行行政网格名称 + 'regionSecGridName', // 支行行政网格名称 + 'belongBranchName', // 行政网格归属支行 + 'belongOutletName', // 行政网格归属网点 + 'belongUserNameList', // 行政网格客户经理 + 'drawGridName', // 自绘地图网格名称 + 'drawBranchNames', // 自绘地图网格归属支行 + 'drawOutletNames', // 自绘地图网格归属网点 + 'drawUserNames', // 自绘地图网格客户经理 + 'virtualGridName', // 自建名单网格名称 + 'virtualBranchNames', // 自建名单网格归属支行 + 'virtualOutletNames', // 自建名单网格归属网点 + 'virtualUserNames', // 自建名单网格客户经理 +] + +const commonCol = function (isCom, type, headId) { + const allColumns = [ { prop: 'regionTopGridName', label: '总行行政网格名称', @@ -101,9 +118,16 @@ const commonCol = function (isCom, type) { showOverflowTooltip: true, }, ] + + // 当headId=875时,过滤掉指定的列 + if (String(headId).startsWith('875')) { + return allColumns.filter(col => !HIDDEN_PROPS_FOR_875.includes(col.prop)) + } + + return allColumns } -export const placeholderMap = (type) => ({ +export const placeholderMap = (type, headId) => ({ '2': { placeholder: '搜索企业名称/法人名字', custPattern: '2', @@ -138,7 +162,7 @@ export const placeholderMap = (type) => ({ type: 'myCustLevel', // desc: '建档输入/取新华社数据', }, - ...commonCol('2', type), + ...commonCol('2', type, headId), { prop: 'lpName', label: '法人姓名', @@ -393,7 +417,7 @@ export const placeholderMap = (type) => ({ type: 'myCustIdsn', // desc: '建档输入/取新华社数据', }, - ...commonCol('1', type), + ...commonCol('1', type, headId), { prop: 'lpName', label: '经营者姓名', @@ -634,7 +658,7 @@ export const placeholderMap = (type) => ({ type: 'myCustLevel', // desc: '取大信贷数据', }, - ...commonCol('0', type), + ...commonCol('0', type, headId), { prop: 'custPhone', label: '联系方式', diff --git a/ruoyi-ui/src/views/grid/charts/customerPerformance/index.vue b/ruoyi-ui/src/views/grid/charts/customerPerformance/index.vue index 26fb259..f0f93c0 100644 --- a/ruoyi-ui/src/views/grid/charts/customerPerformance/index.vue +++ b/ruoyi-ui/src/views/grid/charts/customerPerformance/index.vue @@ -2,9 +2,16 @@
- 企业 // :disabled="isPrivate" - 个人 - 商户 + +
@@ -283,8 +290,9 @@ export default { const type = this.roles.includes('headPublic') ? 'isPublic' : this.roles.includes('headPrivate') ? 'isPrivate' : this.roles.includes('headOps') ? 'isOps' : "" - this.tableColoumns = placeholderMap(type)[val].tableColoumns - this.placeholder = placeholderMap(type)[val].placeholder + const headId = this.deptId + this.tableColoumns = placeholderMap(type, headId)[val].tableColoumns + this.placeholder = placeholderMap(type, headId)[val].placeholder this.queryParams.custPattern = val this.searchColoumns = this.getSearchColoumns() } @@ -310,6 +318,9 @@ export default { }, computed: { ...mapGetters(['roles']), + deptId() { + return this.$store.state.user.deptId + }, filtereDate() { if (this.searchQuery) { return this.tableData.filter((item) => item.companyName.includes(this.searchQuery) || item.legalName.includes(this.searchQuery)) @@ -777,6 +788,12 @@ export default { if (query.backUrl) { this.selectedTab = query.selectedTab this.queryParams.custPattern = query.selectedTab + } else { + // 默认选中第一个tab + const deptId = this.$store.state.user.deptId + const deptIdStr = deptId ? String(deptId) : '' + this.selectedTab = deptIdStr.startsWith('875') ? '0' : '2' + this.queryParams.custPattern = this.selectedTab } }, mounted() { diff --git a/ruoyi-ui/src/views/group/custGroup/components/create-dialog.vue b/ruoyi-ui/src/views/group/custGroup/components/create-dialog.vue new file mode 100644 index 0000000..71f0427 --- /dev/null +++ b/ruoyi-ui/src/views/group/custGroup/components/create-dialog.vue @@ -0,0 +1,680 @@ + + + + + diff --git a/ruoyi-ui/src/views/group/custGroup/detail.vue b/ruoyi-ui/src/views/group/custGroup/detail.vue new file mode 100644 index 0000000..3d8a565 --- /dev/null +++ b/ruoyi-ui/src/views/group/custGroup/detail.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/ruoyi-ui/src/views/group/custGroup/index.vue b/ruoyi-ui/src/views/group/custGroup/index.vue new file mode 100644 index 0000000..8c4b82e --- /dev/null +++ b/ruoyi-ui/src/views/group/custGroup/index.vue @@ -0,0 +1,288 @@ + + + + + diff --git a/ruoyi-ui/src/views/index.vue b/ruoyi-ui/src/views/index.vue index b461293..6572799 100644 --- a/ruoyi-ui/src/views/index.vue +++ b/ruoyi-ui/src/views/index.vue @@ -20,6 +20,7 @@ 较上月变动 {{ changeData(item.inc) }} + {{ changeData(item.inc) }} {{ changeData(item.inc) }}
@@ -43,6 +44,11 @@ @click="goToCustManager(item.itemNm, 'fall')"> {{ item.curAmt }} + + {{ item.curAmt }} + @@ -60,7 +66,7 @@ 预警任务 二次走访提醒 走访资源提醒 - 营销任务 + 营销任务
@@ -152,7 +158,7 @@ - + @@ -194,9 +200,9 @@ - +
@@ -210,7 +216,7 @@

    -
-
+

预警信息

  • @@ -857,6 +863,27 @@ export default { }, showposition() { return this.userName.slice(0, 3) === '875' + }, + // headId为875时隐藏预警信息和营销任务 + shouldHideFor875() { + return this.userName && (this.userName + '').substring(0, 3) === '875' + }, + // headId为875时,便捷操作只显示快速入门 + filteredOptArr() { + // 当deptId以875开头时,只保留快速入门 + if (this.deptId && String(this.deptId).startsWith('875')) { + return this.optArr.filter(item => item.name === '快速入门') + } + return this.optArr + } + }, + watch: { + selectedTab(newVal, oldVal) { + // 切换tab时重置分页为第1页 + if (newVal !== oldVal) { + this.pageNum = 1 + this.agentPageNum = 1 + } } }, mounted() { @@ -1135,8 +1162,7 @@ export default { this.getData() }, handleAgentCurrentChange(val) { - // this.agentPageNum = val - // this.initBranchList() + this.agentPageNum = val this.pageNum = val this.getData() }, @@ -1565,6 +1591,8 @@ p { .page-vr { width: 24%; margin-left: 1%; + display: flex; + flex-direction: column; } .page-title { @@ -1602,7 +1630,11 @@ p { } .page-vr-middle { - margin: 22px 0; + margin-bottom: 22px; +} + +.page-vr-top { + margin-bottom: 22px; } .page-vl-top { @@ -1873,7 +1905,7 @@ p { .page-vr-btm { height: 350px; overflow-y: scroll; - margin-bottom: 30px; + margin-bottom: 22px; } ::v-deep .el-badge__content { @@ -1943,8 +1975,10 @@ p { } .url-box { - max-height: 210px; + flex: 1; overflow-y: scroll; + display: flex; + flex-direction: column; } .yjxxLi { diff --git a/ruoyi-ui/src/views/taskManage/PADvisitRecord/index.vue b/ruoyi-ui/src/views/taskManage/PADvisitRecord/index.vue index eb783f8..3deac86 100644 --- a/ruoyi-ui/src/views/taskManage/PADvisitRecord/index.vue +++ b/ruoyi-ui/src/views/taskManage/PADvisitRecord/index.vue @@ -6,24 +6,46 @@ v-model="selectedTab" @input="handleChange" > - 企业 - 个人 - 商户 + +
    - 企业 - 个人 - 商户 + + -
    - -
    + { - this.dyHeight = Number(window.innerHeight) - 330; - }); - this.dyHeight = Number(window.innerHeight) - 330; this.resetFn(); - this.initCardList() + this.initCardList(); + this.getAlterTypeList(); }, filters: { formatFilter(v, type, list) { @@ -293,6 +299,13 @@ export default { } }, methods: { + getAlterTypeList() { + getAlterTypes().then(res => { + if (res.code === 200) { + this.alterTypeOptions = res.data || []; + } + }); + }, initCardList() { warningCardNum({ reportTime: this.reportTime ? dayjs(this.reportTime).format('YYYY-MM-DD') + ' 23:59:59' : '' }).then(res => { this.cardInfo = res @@ -326,7 +339,6 @@ export default { } else { this.$message.error(response.msg || "操作失败"); } - this.tableKey = !this.tableKey; }); }, currentChangeFn(val) { @@ -369,45 +381,79 @@ export default {