Compare commits
1 Commits
dev
...
fedf789511
| Author | SHA1 | Date | |
|---|---|---|---|
| fedf789511 |
216
CLAUDE.md
216
CLAUDE.md
@@ -1,216 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## 项目概述
|
||||
|
||||
**数字支行辅助管理系统(IBS)** - 基于 若依框架 v3.8.8 的前后端分离全栈项目,专注于银行支行的网格化营销、客户管理和走访业务。
|
||||
|
||||
## 常用命令
|
||||
|
||||
### 后端开发
|
||||
|
||||
```bash
|
||||
# Maven 打包(跳过测试)
|
||||
mvn clean package -Dmaven.test.skip=true
|
||||
|
||||
# 运行已打包的 JAR
|
||||
cd ruoyi-admin/target
|
||||
java -jar -Xms256m -Xmx1024m ruoyi-admin.jar
|
||||
|
||||
# 后端服务地址
|
||||
http://localhost:8080
|
||||
|
||||
# Swagger API 文档
|
||||
http://localhost:8080/swagger-ui/index.html
|
||||
|
||||
# 测试登录接口获取 token
|
||||
POST /login/test?username=admin&password=admin123
|
||||
```
|
||||
|
||||
### 前端开发
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui
|
||||
|
||||
# 安装依赖
|
||||
npm install
|
||||
|
||||
# 开发环境运行(端口 80,代理到 localhost:8080)
|
||||
npm run dev
|
||||
|
||||
# 生产环境构建
|
||||
npm run build:prod
|
||||
|
||||
# 代码检查
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### 数据库连接
|
||||
|
||||
```bash
|
||||
# 通过 MCP MySQL 工具连接
|
||||
# 地址: 116.62.17.81:3306
|
||||
# 数据库: ibs
|
||||
# 用户名: root
|
||||
```
|
||||
|
||||
## 核心架构
|
||||
|
||||
### 后端模块结构
|
||||
|
||||
```
|
||||
ruoyi-admin/ # 主入口模块,包含启动类和配置文件
|
||||
ruoyi-framework/ # 框架核心:安全配置、缓存、数据源等
|
||||
ruoyi-system/ # 系统管理:用户、角色、菜单、字典等
|
||||
ruoyi-common/ # 通用工具:工具类、注解、常量等
|
||||
ruoyi-quartz/ # 定时任务模块
|
||||
ruoyi-generator/ # 代码生成器
|
||||
ibs/ # ★ 业务模块:数字支行核心业务 ★
|
||||
```
|
||||
|
||||
### IBS 业务模块 (核心业务)
|
||||
|
||||
位置: `ibs/src/main/java/com/ruoyi/ibs/`
|
||||
|
||||
**主要业务包:**
|
||||
|
||||
| 包名 | 功能 | 说明 |
|
||||
|------|------|------|
|
||||
| `grid` | 网格管理 | 支行网格划分、分配、统计 |
|
||||
| `cmpm` | 客户经理管理 | 客户经理信息维护 |
|
||||
| `list` | 客户列表管理 | 零售/商户/企业客户管理 |
|
||||
| `visit` | 走访管理 | 走访任务、记录、轨迹 |
|
||||
| `task` | 任务管理 | 营销任务分配和跟踪 |
|
||||
| `draw` | 绘图/网格绘制 | 基于百度地图的网格绘制 |
|
||||
| `custmap` | 客户地图 | 客户地理分布可视化 |
|
||||
| `dashboard` | 仪表盘 | 数据统计和展示 |
|
||||
| `datavisual` | 数据可视化 | 报表和图表 |
|
||||
| `rules` | 规则配置 | 业务规则配置 |
|
||||
| `qxhy` | 青县惠银接口 | 外部系统对接 |
|
||||
| `websocket` | WebSocket通信 | 实时通信支持 |
|
||||
|
||||
**业务模块命名规范:**
|
||||
- 新建模块命名: `ibs` + 主要功能(如 `ibs-grid`, `ibs-customer`)
|
||||
- Controller 放在新建模块中,不与若依框架混合
|
||||
- Entity 使用 `@Data` 注解
|
||||
- Service 使用 `@Resource` 注解,不继承 `ServiceImpl`
|
||||
- DAO 使用 MyBatis Plus,复杂操作在 XML 中编写 SQL
|
||||
|
||||
### 前端结构
|
||||
|
||||
```
|
||||
ruoyi-ui/src/
|
||||
├── api/ # API 接口定义
|
||||
├── views/ # 页面视图
|
||||
│ ├── grid/ # 网格管理相关页面
|
||||
│ ├── customer/ # 客户管理
|
||||
│ ├── taskManage/ # 任务管理
|
||||
│ ├── dashboard/ # 仪表盘
|
||||
│ └── ...
|
||||
├── components/ # 公共组件
|
||||
├── map/ # 地图相关(百度地图集成)
|
||||
├── store/ # Vuex 状态管理
|
||||
└── router/ # 路由配置
|
||||
```
|
||||
|
||||
### 数据库表命名规范
|
||||
|
||||
- 新建表需加项目前缀: `ibs_` + 表名
|
||||
- 示例: `ibs_grid`, `ibs_customer`, `ibs_visit_record`
|
||||
|
||||
## 关键技术点
|
||||
|
||||
### 1. 地图集成
|
||||
|
||||
项目深度集成百度地图 API,用于:
|
||||
- 网格绘制和编辑
|
||||
- 客户地理位置标注
|
||||
- 走访轨迹记录
|
||||
- 客户分布热力图
|
||||
|
||||
**相关配置:**
|
||||
- 百度地图 AK 在前端配置
|
||||
- 使用 JTS 库进行地理空间计算
|
||||
|
||||
### 2. 批量导入优化
|
||||
|
||||
设计批量导入功能时:
|
||||
- 使用批量操作提高响应速度
|
||||
- 导入结果只展示失败数据,不展示成功数据
|
||||
- 使用 EasyExcel 处理 Excel
|
||||
|
||||
### 3. 多端支持
|
||||
|
||||
- PC 端: 主要管理和配置功能
|
||||
- PAD 端: 走访记录功能(移动端)
|
||||
|
||||
### 4. 外部系统对接
|
||||
|
||||
| 系统 | 地址 | 用途 |
|
||||
|------|------|------|
|
||||
| 青县惠银 | http://158.234.96.76:5002 | 业务数据对接 |
|
||||
| BI 系统 | http://158.220.52.42:9388/bi | 数据分析和报表 |
|
||||
| 阿里云 OSS | - | 文件存储 |
|
||||
|
||||
## 开发规范
|
||||
|
||||
### 代码分层
|
||||
|
||||
- **Entity**: 实体类,不继承 BaseEntity,单独添加审计字段
|
||||
- **DTO**: 接口传参专用类
|
||||
- **VO**: 返回数据专用类
|
||||
- **Service**: 业务逻辑,使用 `@Resource` 注入
|
||||
- **Mapper**: MyBatis Plus + XML 混合使用
|
||||
|
||||
### 审计字段
|
||||
|
||||
通过注解实现自动填充:
|
||||
```java
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
```
|
||||
|
||||
### 分页
|
||||
|
||||
使用 MyBatis Plus 分页插件自动处理,传入 `Page` 对象即可。
|
||||
|
||||
### 前端菜单配置
|
||||
|
||||
添加新页面后,需要在数据库 `sys_menu` 表中配置菜单权限。
|
||||
|
||||
## 重要配置文件
|
||||
|
||||
| 文件 | 位置 | 说明 |
|
||||
|------|------|------|
|
||||
| 后端配置 | `ruoyi-admin/src/main/resources/application-dev.yml` | 数据库、Redis、第三方服务配置 |
|
||||
| 前端配置 | `ruoyi-ui/vue.config.js` | 开发服务器、代理配置 |
|
||||
| 菜单数据 | 数据库 `sys_menu` 表 | 菜单权限配置 |
|
||||
|
||||
## 测试账号
|
||||
|
||||
- 用户名: `admin`
|
||||
- 密码: `admin123`
|
||||
|
||||
## Swagger 接口文档
|
||||
|
||||
启动后端后访问: `http://localhost:8080/swagger-ui/index.html`
|
||||
|
||||
## 开发流程建议
|
||||
|
||||
1. **数据库设计**: 新建表需加 `ibs_` 前缀
|
||||
2. **后端实体类**: 使用 `@Data` 注解,添加审计字段注解
|
||||
3. **后端业务层**: 在 `ibs` 模块下开发,简单 CRUD 用 MyBatis Plus,复杂操作用 XML
|
||||
4. **后端测试**: 使用 `/login/test` 获取 token 后测试接口
|
||||
5. **前端开发**: 在 `ruoyi-ui/views/` 下对应业务模块开发
|
||||
6. **菜单配置**: 在 `sys_menu` 表添加菜单项
|
||||
7. **API 文档**: 生成后保存在 `doc/api/` 目录
|
||||
@@ -1,308 +0,0 @@
|
||||
# 特色区域详情窗口添加查看客户功能设计文档
|
||||
|
||||
## 文档信息
|
||||
|
||||
- **日期**: 2026-02-28
|
||||
- **作者**: Claude Code
|
||||
- **模块**: 区域绘制 - 特色区域
|
||||
- **需求来源**: 用户需求
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 背景
|
||||
|
||||
数字支行辅助管理系统的区域绘制功能包含两种区域类型:
|
||||
1. **行政区域**(btnType=1):使用 index.vue 页面,区域详情窗口已有"查看客户"按钮
|
||||
2. **特色区域**(btnType=2):使用 BMapPolygonEditor.vue 组件,区域详情窗口缺少"查看客户"功能
|
||||
|
||||
### 1.2 目标
|
||||
|
||||
在特色区域的区域详情窗口中,添加"查看客户"按钮,使功能与行政区域保持一致,方便用户快速查看该区域内的客户信息。
|
||||
|
||||
### 1.3 范围
|
||||
|
||||
**涉及模块:**
|
||||
- 前端:`ruoyi-ui/src/map/BMapPolygonEditor.vue`
|
||||
- 复用组件:`ruoyi-ui/src/views/grid/map/draw-area/customer-modal.vue`
|
||||
|
||||
**不涉及:**
|
||||
- 后端 API 修改
|
||||
- 数据库修改
|
||||
- 新增文件
|
||||
|
||||
## 2. 需求分析
|
||||
|
||||
### 2.1 用户需求
|
||||
|
||||
在区域绘制的特色区域详情窗口内,添加一个查看客户的按钮,功能与特色区域列表(menulist-modal)中的查看客户功能保持一致。
|
||||
|
||||
### 2.2 功能需求
|
||||
|
||||
#### 必须实现(MVP)
|
||||
- ✅ 在区域详情窗口的按钮区域添加"查看客户"图标按钮
|
||||
- ✅ 点击按钮后打开客户查看模态框
|
||||
- ✅ 模态框中显示该特色区域的企业、个人、商户客户列表
|
||||
- ✅ 支持客户类型切换(企业/个人/商户)
|
||||
- ✅ 支持分页查询
|
||||
- ✅ 支持点击客户名称跳转到客户详情页
|
||||
|
||||
#### 可选功能
|
||||
- 无
|
||||
|
||||
### 2.3 非功能需求
|
||||
|
||||
- **性能**: 不影响页面加载速度
|
||||
- **易用性**: 图标按钮风格与现有按钮保持一致
|
||||
- **兼容性**: 不影响现有功能
|
||||
- **可维护性**: 代码结构清晰,复用现有组件
|
||||
|
||||
## 3. 技术设计
|
||||
|
||||
### 3.1 整体架构
|
||||
|
||||
采用组件复用架构:
|
||||
- **展示层**: BMapPolygonEditor.vue(特色区域地图编辑器)
|
||||
- **组件层**: CustomerModal.vue(客户查看模态框,已存在)
|
||||
- **数据层**: API 接口(shapeCustList,已存在)
|
||||
|
||||
### 3.2 方案选择
|
||||
|
||||
经过方案对比分析,选择**方案1:添加图标按钮**
|
||||
|
||||
**方案对比:**
|
||||
|
||||
| 方案 | 优点 | 缺点 | 评分 |
|
||||
|------|------|------|------|
|
||||
| 方案1:图标按钮 | UI风格一致、改动最小、实现简单 | 图标不如文字明显 | ★★★★★ |
|
||||
| 方案2:文字按钮 | 按钮明显、文字清晰 | 可能破坏布局、需要调整样式 | ★★★☆☆ |
|
||||
| 方案3:替换布局 | 与行政区域一致 | 改动大、影响用户体验 | ★★☆☆☆ |
|
||||
|
||||
### 3.3 数据流程
|
||||
|
||||
```
|
||||
用户操作流程:
|
||||
用户点击特色区域
|
||||
↓
|
||||
显示区域详情窗口(area-info-modal)
|
||||
↓
|
||||
用户点击"查看客户"图标按钮
|
||||
↓
|
||||
触发 previewCustomer() 方法
|
||||
↓
|
||||
调用 this.$refs.customerModal.onOpen()
|
||||
↓
|
||||
CustomerModal 组件接收参数:
|
||||
- cardType="featured" (标识特色区域)
|
||||
- :detailInfo="areaForm" (包含 shapeId)
|
||||
- :btnType="'2'" (特色区域类型)
|
||||
↓
|
||||
CustomerModal 调用 shapeCustList API
|
||||
↓
|
||||
获取并展示客户列表数据
|
||||
↓
|
||||
用户可切换客户类型(企业/个人/商户)
|
||||
↓
|
||||
用户可点击客户名称查看详情
|
||||
```
|
||||
|
||||
### 3.4 接口设计
|
||||
|
||||
**使用现有接口:**
|
||||
|
||||
#### 获取特色区域客户列表
|
||||
- **接口**: `shapeCustList`
|
||||
- **文件**: `@/api/grid/draw-area.js`
|
||||
- **参数**:
|
||||
- shapeId: 区域ID
|
||||
- pageNum: 页码
|
||||
- pageSize: 每页条数
|
||||
- custType: 客户类型(0=个人,1=商户,2=企业)
|
||||
- **返回**: 客户列表数据
|
||||
|
||||
**无需新增接口。**
|
||||
|
||||
### 3.5 组件设计
|
||||
|
||||
#### BMapPolygonEditor.vue 修改
|
||||
|
||||
**1. 引入组件**
|
||||
|
||||
```javascript
|
||||
import CustomerModal from "@/views/grid/map/draw-area/customer-modal.vue"
|
||||
|
||||
export default {
|
||||
components: {
|
||||
// ... 现有组件
|
||||
CustomerModal,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**2. 添加组件引用**
|
||||
|
||||
在 template 末尾(约第273行之后)添加:
|
||||
|
||||
```vue
|
||||
<customer-modal
|
||||
ref="customerModal"
|
||||
cardType="featured"
|
||||
:detailInfo="areaForm"
|
||||
:btnType="'2'"
|
||||
/>
|
||||
```
|
||||
|
||||
**3. 添加图标按钮**
|
||||
|
||||
在区域详情窗口的按钮区域(第221-270行),建议位置在"修改信息"按钮之后:
|
||||
|
||||
```vue
|
||||
<el-tooltip placement="top" effect="light" content="查看客户">
|
||||
<i
|
||||
class="el-icon-user icon-area"
|
||||
@click.stop="previewCustomer"
|
||||
/>
|
||||
</el-tooltip>
|
||||
```
|
||||
|
||||
**4. 添加方法**
|
||||
|
||||
```javascript
|
||||
methods: {
|
||||
// ... 现有方法
|
||||
|
||||
/**
|
||||
* 查看客户
|
||||
*/
|
||||
previewCustomer() {
|
||||
this.$refs.customerModal.onOpen()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### CustomerModal.vue(无需修改)
|
||||
|
||||
该组件已实现完整的客户查看功能:
|
||||
- 支持行政区域(通过 code 查询)
|
||||
- 支持特色区域(通过 shapeId 查询)
|
||||
- 支持三种客户类型切换
|
||||
- 支持分页
|
||||
- 支持跳转客户详情
|
||||
|
||||
## 4. 实施计划
|
||||
|
||||
### 4.1 开发任务
|
||||
|
||||
| 任务 | 文件 | 预估时间 | 负责人 |
|
||||
|------|------|---------|--------|
|
||||
| 添加"查看客户"功能 | BMapPolygonEditor.vue | 0.5h | 前端开发 |
|
||||
| 功能测试 | - | 0.5h | 测试人员 |
|
||||
|
||||
**总计**: 1小时
|
||||
|
||||
### 4.2 测试计划
|
||||
|
||||
#### 单元测试
|
||||
- 测试 previewCustomer() 方法是否正确调用
|
||||
- 测试组件引用是否正确传递参数
|
||||
|
||||
#### 集成测试
|
||||
- 测试点击按钮后模态框是否正常打开
|
||||
- 测试客户列表是否正确加载
|
||||
- 测试客户类型切换功能
|
||||
- 测试分页功能
|
||||
|
||||
#### UI测试
|
||||
- 测试图标样式是否一致
|
||||
- 测试 tooltip 是否正确显示
|
||||
- 测试响应式布局
|
||||
|
||||
#### 回归测试
|
||||
- 验证现有4个图标按钮功能正常
|
||||
- 验证行政区域的查看客户功能正常
|
||||
- 验证页面无报错
|
||||
|
||||
### 4.3 部署计划
|
||||
|
||||
- **开发环境**: 开发完成后立即部署测试
|
||||
- **测试环境**: 通过代码审查后部署
|
||||
- **生产环境**: 测试通过后部署
|
||||
|
||||
## 5. 风险评估
|
||||
|
||||
### 5.1 技术风险
|
||||
|
||||
| 风险 | 等级 | 影响 | 缓解措施 |
|
||||
|------|------|------|----------|
|
||||
| 组件引入导致页面加载变慢 | 低 | 轻微 | 组件已在使用,无额外性能影响 |
|
||||
| 图标样式不一致 | 低 | 轻微 | 使用现有 icon-area 样式类 |
|
||||
| 数据传递错误 | 中 | 中等 | 充分测试,确保 shapeId 正确传递 |
|
||||
|
||||
### 5.2 业务风险
|
||||
|
||||
| 风险 | 等级 | 影响 | 缓解措施 |
|
||||
|------|------|------|----------|
|
||||
| 用户体验变化 | 低 | 轻微 | 仅新增功能,不影响现有操作 |
|
||||
| 功能误解 | 低 | 轻微 | tooltip 提示清晰 |
|
||||
|
||||
## 6. 验收标准
|
||||
|
||||
### 6.1 功能验收
|
||||
|
||||
- [ ] 区域详情窗口中显示"查看客户"图标按钮
|
||||
- [ ] 点击按钮后成功打开客户查看模态框
|
||||
- [ ] 模态框顶部显示正确的区域名称和规模
|
||||
- [ ] 客户列表正确加载(企业/个人/商户三种类型)
|
||||
- [ ] 客户类型切换功能正常
|
||||
- [ ] 分页功能正常
|
||||
- [ ] 点击客户名称可跳转到客户详情页
|
||||
|
||||
### 6.2 UI验收
|
||||
|
||||
- [ ] 图标样式与现有图标按钮一致
|
||||
- [ ] 图标大小、颜色、间距合理
|
||||
- [ ] Tooltip 正确显示"查看客户"
|
||||
- [ ] 图标位置合理,不拥挤
|
||||
|
||||
### 6.3 性能验收
|
||||
|
||||
- [ ] 页面加载速度无明显变化
|
||||
- [ ] 模态框打开速度正常(<1秒)
|
||||
- [ ] 客户列表加载速度正常(<2秒)
|
||||
|
||||
### 6.4 兼容性验收
|
||||
|
||||
- [ ] 现有4个图标按钮功能正常
|
||||
- [ ] 行政区域的查看客户功能正常
|
||||
- [ ] 浏览器控制台无报错
|
||||
|
||||
## 7. 后续优化
|
||||
|
||||
### 7.1 短期优化(可选)
|
||||
|
||||
- 无
|
||||
|
||||
### 7.2 长期优化(可选)
|
||||
|
||||
1. **统一按钮风格**: 考虑将所有图标按钮改为文字按钮,提高可读性
|
||||
2. **权限控制**: 根据用户角色控制"查看客户"按钮的显示/隐藏
|
||||
3. **数据缓存**: 对频繁查看的区域客户数据进行缓存,提升加载速度
|
||||
|
||||
## 8. 附录
|
||||
|
||||
### 8.1 相关文件
|
||||
|
||||
- `ruoyi-ui/src/map/BMapPolygonEditor.vue` - 特色区域地图编辑器
|
||||
- `ruoyi-ui/src/views/grid/map/draw-area/customer-modal.vue` - 客户查看模态框
|
||||
- `ruoyi-ui/src/views/grid/map/draw-area/components/menulist-modal.vue` - 特色区域列表
|
||||
- `ruoyi-ui/src/views/grid/map/draw-area/index.vue` - 行政区域页面
|
||||
|
||||
### 8.2 参考资料
|
||||
|
||||
- Element UI 文档: https://element.eleme.cn/
|
||||
- 若依框架文档: http://doc.ruoyi.vip/
|
||||
|
||||
### 8.3 变更记录
|
||||
|
||||
| 版本 | 日期 | 修改人 | 修改内容 |
|
||||
|------|------|--------|----------|
|
||||
| 1.0 | 2026-02-28 | Claude Code | 初始版本 |
|
||||
@@ -1,389 +0,0 @@
|
||||
# 特色区域查看客户功能实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**目标:** 在特色区域详情窗口添加"查看客户"图标按钮,复用现有的 customer-modal 组件,实现客户列表查看功能。
|
||||
|
||||
**架构:** 组件复用架构 - BMapPolygonEditor.vue 引入 CustomerModal.vue,通过 refs 调用模态框的 onOpen() 方法。
|
||||
|
||||
**技术栈:** Vue 2.x, Element UI, 若依框架
|
||||
|
||||
**设计文档:** `docs/plans/2026-02-28-featured-area-customer-view-design.md`
|
||||
|
||||
---
|
||||
|
||||
## 任务 1: 引入 CustomerModal 组件
|
||||
|
||||
**文件:**
|
||||
- 修改: `ruoyi-ui/src/map/BMapPolygonEditor.vue:20-30`(import 区域)
|
||||
|
||||
**步骤 1: 在 script 标签内添加 import 语句**
|
||||
|
||||
在第22行(MenuEdit 导入语句之后)添加:
|
||||
|
||||
```javascript
|
||||
import CustomerModal from "@/views/grid/map/draw-area/customer-modal.vue"
|
||||
```
|
||||
|
||||
**步骤 2: 在 components 中注册组件**
|
||||
|
||||
在第26-30行的 components 对象中添加 CustomerModal:
|
||||
|
||||
```javascript
|
||||
components: {
|
||||
MenuEdit,
|
||||
BMapPolygonEditor,
|
||||
MenuEdit,
|
||||
CustomerModal, // 新增
|
||||
},
|
||||
```
|
||||
|
||||
**步骤 3: 验证 import 路径正确**
|
||||
|
||||
运行前端项目验证无报错:
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui
|
||||
npm run dev
|
||||
```
|
||||
|
||||
预期: 浏览器控制台无 "Failed to mount component" 错误
|
||||
|
||||
**步骤 4: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/map/BMapPolygonEditor.vue
|
||||
git commit -m "feat(featured-areas): 引入 CustomerModal 组件"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 任务 2: 在 template 中添加组件引用
|
||||
|
||||
**文件:**
|
||||
- 修改: `ruoyi-ui/src/map/BMapPolygonEditor.vue:270-275`(template 末尾)
|
||||
|
||||
**步骤 1: 在 template 末尾添加组件标签**
|
||||
|
||||
在第273行(`</transition>` 之后,`<div class="search-box">` 之前)添加:
|
||||
|
||||
```vue
|
||||
</transition>
|
||||
<!-- 查看客户模态框 -->
|
||||
<customer-modal
|
||||
ref="customerModal"
|
||||
cardType="featured"
|
||||
:detailInfo="areaForm"
|
||||
:btnType="'2'"
|
||||
/>
|
||||
<div class="search-box">
|
||||
```
|
||||
|
||||
**步骤 2: 验证组件引用**
|
||||
|
||||
在浏览器中打开特色区域页面,验证控制台无报错。
|
||||
|
||||
**步骤 3: 验证 props 传递**
|
||||
|
||||
在 Vue DevTools 中检查 customer-modal 组件的 props:
|
||||
- cardType 应该是 "featured"
|
||||
- detailInfo 应该是 areaForm 对象
|
||||
- btnType 应该是 "2"
|
||||
|
||||
**步骤 4: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/map/BMapPolygonEditor.vue
|
||||
git commit -m "feat(featured-areas): 添加 CustomerModal 组件引用"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 任务 3: 在区域详情窗口添加查看客户图标按钮
|
||||
|
||||
**文件:**
|
||||
- 修改: `ruoyi-ui/src/map/BMapPolygonEditor.vue:221-240`(edit-operate 按钮区域)
|
||||
|
||||
**步骤 1: 定位按钮插入位置**
|
||||
|
||||
找到第221-240行的代码区域,这是 `infoType === 'SHOW'` 的按钮区域:
|
||||
- 第229行:修改信息按钮结束
|
||||
- 第240行:删除区域按钮开始
|
||||
|
||||
我们需要在修改信息按钮之后插入查看客户按钮。
|
||||
|
||||
**步骤 2: 添加查看客户图标按钮**
|
||||
|
||||
在第239行(修改信息 tooltip 结束)之后,第240行(删除区域 tooltip 开始)之前插入:
|
||||
|
||||
```vue
|
||||
</el-tooltip>
|
||||
<!-- 查看客户 -->
|
||||
<el-tooltip placement="top" effect="light" content="查看客户">
|
||||
<i
|
||||
class="el-icon-user icon-area"
|
||||
@click.stop="previewCustomer"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<!-- 删除区域 -->
|
||||
<el-tooltip placement="top" effect="light" content="删除区域">
|
||||
```
|
||||
|
||||
**步骤 3: 验证图标样式**
|
||||
|
||||
在浏览器中:
|
||||
1. 打开特色区域页面
|
||||
2. 点击一个已绘制的区域
|
||||
3. 查看区域详情窗口底部的图标按钮区域
|
||||
4. 验证新添加的"用户图标"样式与其他图标一致
|
||||
|
||||
**步骤 4: 验证 tooltip 显示**
|
||||
|
||||
1. 鼠标悬停在新添加的用户图标上
|
||||
2. 验证 tooltip 显示"查看客户"
|
||||
3. 验证 tooltip 样式与其他 tooltip 一致
|
||||
|
||||
**步骤 5: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/map/BMapPolygonEditor.vue
|
||||
git commit -m "feat(featured-areas): 添加查看客户图标按钮"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 任务 4: 实现 previewCustomer 方法
|
||||
|
||||
**文件:**
|
||||
- 修改: `ruoyi-ui/src/map/BMapPolygonEditor.vue:900-1000`(methods 区域)
|
||||
|
||||
**步骤 1: 定位 methods 区域**
|
||||
|
||||
找到 methods 对象的最后,准备添加新方法。建议添加在 `updateAreaShape()` 方法之后。
|
||||
|
||||
**步骤 2: 添加 previewCustomer 方法**
|
||||
|
||||
在 methods 对象中添加:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* 调整边界
|
||||
*/
|
||||
updateAreaShape() {
|
||||
// ... 现有代码
|
||||
},
|
||||
/**
|
||||
* 查看客户
|
||||
*/
|
||||
previewCustomer() {
|
||||
this.$refs.customerModal.onOpen()
|
||||
}
|
||||
```
|
||||
|
||||
**步骤 3: 验证方法调用**
|
||||
|
||||
在浏览器控制台中测试:
|
||||
1. 打开特色区域页面
|
||||
2. 点击一个区域,打开区域详情窗口
|
||||
3. 打开 Vue DevTools
|
||||
4. 找到 BMapPolygonEditor 组件
|
||||
5. 在控制台执行:`$vm0.previewCustomer()`
|
||||
6. 验证 customer-modal 模态框成功打开
|
||||
|
||||
**步骤 4: 提交代码**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/map/BMapPolygonEditor.vue
|
||||
git commit -m "feat(featured-areas): 实现 previewCustomer 方法"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 任务 5: 功能测试
|
||||
|
||||
**文件:**
|
||||
- 无需修改文件
|
||||
|
||||
**步骤 1: 启动前端项目**
|
||||
|
||||
```bash
|
||||
cd ruoyi-ui
|
||||
npm run dev
|
||||
```
|
||||
|
||||
预期: 项目成功启动在 http://localhost:80
|
||||
|
||||
**步骤 2: 登录系统**
|
||||
|
||||
1. 访问 http://localhost:80
|
||||
2. 使用测试账号登录:
|
||||
- 用户名: admin
|
||||
- 密码: admin123
|
||||
|
||||
**步骤 3: 进入特色区域页面**
|
||||
|
||||
1. 导航到"网格管理" > "区域绘制"
|
||||
2. 点击"特色区域"标签(btnType=2)
|
||||
3. 等待地图加载完成
|
||||
|
||||
**步骤 4: 测试查看客户按钮**
|
||||
|
||||
1. 在左侧菜单中选择一个特色区域图层
|
||||
2. 在地图上点击一个已绘制的特色区域
|
||||
3. 验证右上角弹出区域详情窗口
|
||||
4. 查看窗口底部的图标按钮区域
|
||||
5. 验证显示"用户图标"(查看客户按钮)
|
||||
|
||||
**步骤 5: 测试点击功能**
|
||||
|
||||
1. 点击"查看客户"图标
|
||||
2. 验证:
|
||||
- customer-modal 对话框成功打开
|
||||
- 对话框标题显示"查看客户"
|
||||
- 顶部显示区域名称和规模信息
|
||||
- 客户列表成功加载
|
||||
- 显示三种客户类型标签(企业/个人/商户)
|
||||
|
||||
**步骤 6: 测试客户类型切换**
|
||||
|
||||
1. 点击"个人"标签
|
||||
2. 验证客户列表刷新,显示个人客户
|
||||
3. 点击"商户"标签
|
||||
4. 验证客户列表刷新,显示商户客户
|
||||
5. 点击"企业"标签
|
||||
6. 验证客户列表刷新,显示企业客户
|
||||
|
||||
**步骤 7: 测试分页功能**
|
||||
|
||||
1. 如果客户数量超过10条,验证分页器显示
|
||||
2. 点击下一页
|
||||
3. 验证客户列表刷新,显示第二页数据
|
||||
4. 修改每页显示条数
|
||||
5. 验证列表重新加载
|
||||
|
||||
**步骤 8: 测试客户详情跳转**
|
||||
|
||||
1. 点击表格中的客户名称(el-button)
|
||||
2. 验证路由跳转到客户详情页面
|
||||
3. 验证详情页面显示正确的客户信息
|
||||
|
||||
**步骤 9: 测试现有功能兼容性**
|
||||
|
||||
1. 关闭客户模态框
|
||||
2. 依次点击其他4个图标按钮:
|
||||
- 重新申请
|
||||
- 修改信息
|
||||
- 删除区域
|
||||
- 调整边界
|
||||
3. 验证这些按钮功能正常,无报错
|
||||
|
||||
**步骤 10: 测试行政区域兼容性**
|
||||
|
||||
1. 切换到"行政区域"标签(btnType=1)
|
||||
2. 点击一个行政区域
|
||||
3. 验证行政区域的"查看客户"按钮功能正常
|
||||
4. 验证两个区域类型的查看客户功能互不影响
|
||||
|
||||
**步骤 11: 检查浏览器控制台**
|
||||
|
||||
1. 打开浏览器开发者工具
|
||||
2. 切换到 Console 标签
|
||||
3. 执行所有测试步骤
|
||||
4. 验证控制台无 JavaScript 报错
|
||||
5. 验证控制台无 Vue 警告
|
||||
|
||||
**步骤 12: 最终提交**
|
||||
|
||||
如果所有测试通过:
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
预期: 无未提交的文件
|
||||
|
||||
---
|
||||
|
||||
## 任务 6: 更新文档(可选)
|
||||
|
||||
**文件:**
|
||||
- 修改: `docs/plans/2026-02-28-featured-area-customer-view-design.md`
|
||||
|
||||
**步骤 1: 更新验收标准**
|
||||
|
||||
在验收标准的各项前打勾:
|
||||
|
||||
```markdown
|
||||
### 6.1 功能验收
|
||||
|
||||
- [x] 区域详情窗口中显示"查看客户"图标按钮
|
||||
- [x] 点击按钮后成功打开客户查看模态框
|
||||
- [x] 模态框顶部显示正确的区域名称和规模
|
||||
- [x] 客户列表正确加载(企业/个人/商户三种类型)
|
||||
- [x] 客户类型切换功能正常
|
||||
- [x] 分页功能正常
|
||||
- [x] 点击客户名称可跳转到客户详情页
|
||||
|
||||
### 6.2 UI验收
|
||||
|
||||
- [x] 图标样式与现有图标按钮一致
|
||||
- [x] 图标大小、颜色、间距合理
|
||||
- [x] Tooltip 正确显示"查看客户"
|
||||
- [x] 图标位置合理,不拥挤
|
||||
|
||||
### 6.3 性能验收
|
||||
|
||||
- [x] 页面加载速度无明显变化
|
||||
- [x] 模态框打开速度正常(<1秒)
|
||||
- [x] 客户列表加载速度正常(<2秒)
|
||||
|
||||
### 6.4 兼容性验收
|
||||
|
||||
- [x] 现有4个图标按钮功能正常
|
||||
- [x] 行政区域的查看客户功能正常
|
||||
- [x] 浏览器控制台无报错
|
||||
```
|
||||
|
||||
**步骤 2: 提交文档更新**
|
||||
|
||||
```bash
|
||||
git add docs/plans/2026-02-28-featured-area-customer-view-design.md
|
||||
git commit -m "docs: 更新特色区域查看客户功能验收状态"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完成检查清单
|
||||
|
||||
实施完成后,验证以下内容:
|
||||
|
||||
- [ ] 所有6个任务已完成
|
||||
- [ ] 所有 git commits 已提交
|
||||
- [ ] 前端项目无编译错误
|
||||
- [ ] 浏览器控制台无 JavaScript 错误
|
||||
- [ ] 功能测试全部通过
|
||||
- [ ] 代码已推送到远程仓库(如需要)
|
||||
|
||||
## 回滚方案
|
||||
|
||||
如果实施过程中遇到问题,可以回滚:
|
||||
|
||||
```bash
|
||||
# 查看提交历史
|
||||
git log --oneline
|
||||
|
||||
# 回滚到指定提交(替换 <commit-hash> 为实际的 commit hash)
|
||||
git reset --hard <commit-hash>
|
||||
|
||||
# 或者回滚所有提交(回到初始状态)
|
||||
git reset --hard HEAD~4
|
||||
```
|
||||
|
||||
## 相关资源
|
||||
|
||||
- **设计文档**: `docs/plans/2026-02-28-featured-area-customer-view-design.md`
|
||||
- **修改文件**: `ruoyi-ui/src/map/BMapPolygonEditor.vue`
|
||||
- **复用组件**: `ruoyi-ui/src/views/grid/map/draw-area/customer-modal.vue`
|
||||
- **参考实现**: `ruoyi-ui/src/views/grid/map/draw-area/components/menulist-modal.vue:413-416`
|
||||
- **Element UI 文档**: https://element.eleme.cn/#/zh-CN/component/tooltip
|
||||
- **Vue 2.x 文档**: https://v2.cn.vuejs.org/
|
||||
88
ibs-group/pom.xml
Normal file
88
ibs-group/pom.xml
Normal file
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<version>3.8.8</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ibs-group</artifactId>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- Mockito依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>3.3.3</version> <!-- 请根据需要选择合适的版本 -->
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- JUnit依赖 -->
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version> <!-- 请根据需要选择合适的版本 -->
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- 通用工具-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-common</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.locationtech.jts</groupId>
|
||||
<artifactId>jts-core</artifactId>
|
||||
<version>1.18.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-webmvc</artifactId>
|
||||
<version>5.3.31</version>
|
||||
</dependency>
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.postgresql</groupId>-->
|
||||
<!-- <artifactId>postgresql</artifactId>-->
|
||||
<!-- <version>42.3.3</version>-->
|
||||
<!-- </dependency>-->
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-annotations</artifactId>
|
||||
<version>1.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-system</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>easyexcel-core</artifactId>
|
||||
<version>3.3.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.opencsv</groupId>
|
||||
<artifactId>opencsv</artifactId>
|
||||
<version>5.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ibs</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.ruoyi.group.controller;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
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.common.utils.poi.ExcelUtil;
|
||||
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.vo.CustGroupVO;
|
||||
import com.ruoyi.group.service.ICustGroupService;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 客群管理Controller
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Api(tags = "客群管理接口")
|
||||
@RestController
|
||||
@RequestMapping("/group/cust")
|
||||
public class CustGroupController extends BaseController {
|
||||
|
||||
@Resource
|
||||
private ICustGroupService custGroupService;
|
||||
|
||||
/**
|
||||
* 查询客群列表
|
||||
*/
|
||||
@ApiOperation("查询客群列表")
|
||||
@Log(title = "客群管理-查询客群列表")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo listCustGroup(CustGroupQueryDTO dto) {
|
||||
startPage();
|
||||
List<CustGroupVO> list = custGroupService.listCustGroup(dto);
|
||||
return getDataTable(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客群详情
|
||||
*/
|
||||
@ApiOperation("获取客群详情")
|
||||
@Log(title = "客群管理-获取客群详情")
|
||||
@GetMapping("/{id}")
|
||||
public AjaxResult getCustGroup(@PathVariable Long id) {
|
||||
CustGroupVO vo = custGroupService.getCustGroup(id);
|
||||
return AjaxResult.success(vo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步创建客群(网格导入)
|
||||
*/
|
||||
@ApiOperation("异步创建客群(网格导入)")
|
||||
@Log(title = "客群管理-网格导入创建客群", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/createByGrid")
|
||||
public AjaxResult createCustGroupByGrid(@RequestBody @Valid GridImportDTO gridImportDTO) {
|
||||
String id = custGroupService.createCustGroupByGrid(gridImportDTO);
|
||||
return AjaxResult.success(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步创建客群(模板导入)
|
||||
*/
|
||||
@ApiOperation("异步创建客群(模板导入)")
|
||||
@Log(title = "客群管理-异步创建客群", businessType = BusinessType.INSERT)
|
||||
@PostMapping(value = "/createByTemplate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public AjaxResult createCustGroupByTemplate(@RequestPart("dto") @Valid String dtoJson,
|
||||
@RequestPart("file") MultipartFile file) {
|
||||
CustGroup custGroup = JSON.parseObject(dtoJson, CustGroup.class);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新客群(网格导入)
|
||||
*/
|
||||
@ApiOperation("更新客群(网格导入)")
|
||||
@Log(title = "客群管理-更新客群(网格导入)", businessType = BusinessType.UPDATE)
|
||||
@PostMapping("/updateByGrid")
|
||||
public AjaxResult updateCustGroupByGrid(@RequestBody @Valid GridImportDTO gridImportDTO) {
|
||||
String result = custGroupService.updateCustGroupByGrid(gridImportDTO);
|
||||
return AjaxResult.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新客群(模板导入)
|
||||
*/
|
||||
@ApiOperation("更新客群(模板导入)")
|
||||
@Log(title = "客群管理-更新客群(模板导入)", businessType = BusinessType.UPDATE)
|
||||
@PostMapping(value = "/updateByTemplate", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public AjaxResult updateCustGroupByTemplate(@RequestPart("dto") @Valid String dtoJson,
|
||||
@RequestPart("file") MultipartFile file) {
|
||||
CustGroup custGroup = JSON.parseObject(dtoJson, CustGroup.class);
|
||||
return AjaxResult.success(custGroupService.updateCustGroupByTemplate(custGroup, file));
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮询客群创建状态
|
||||
*/
|
||||
@ApiOperation("轮询客群创建状态")
|
||||
@Log(title = "客群管理-轮询客群创建状态")
|
||||
@GetMapping("/createStatus/{id}")
|
||||
public AjaxResult getCreateStatus(@PathVariable Long id) {
|
||||
String status = custGroupService.getCreateStatus(id);
|
||||
return AjaxResult.success(status);
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户信息模板下载
|
||||
*/
|
||||
@ApiOperation("客户信息模板")
|
||||
@Log(title = "客群管理-客户信息模板", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/download")
|
||||
public void download(HttpServletResponse response) {
|
||||
ExcelUtil<CustGroupMemberTemplate> util = new ExcelUtil<>(CustGroupMemberTemplate.class);
|
||||
util.exportExcel(response, null, "客户信息模板");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除客群
|
||||
*/
|
||||
@ApiOperation("删除客群")
|
||||
@Log(title = "客群管理-删除客群", businessType = BusinessType.DELETE)
|
||||
@PostMapping("/delete")
|
||||
public AjaxResult deleteCustGroup(@RequestBody List<Long> idList) {
|
||||
String result = custGroupService.deleteCustGroup(idList);
|
||||
return AjaxResult.success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动移除客群客户
|
||||
*/
|
||||
@ApiOperation("手动移除客群客户")
|
||||
@Log(title = "客群管理-手动移除客户", businessType = BusinessType.DELETE)
|
||||
@PostMapping("/removeMembers")
|
||||
public AjaxResult removeMembers(@RequestParam Long groupId, @RequestBody List<Long> memberIds) {
|
||||
String result = custGroupService.removeMembers(groupId, memberIds);
|
||||
return AjaxResult.success(result);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.ruoyi.group.domain.dto;
|
||||
|
||||
import com.ruoyi.common.annotation.Excel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 客群客户Excel模板
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
public class CustGroupMemberTemplate {
|
||||
|
||||
/**
|
||||
* 客户类型
|
||||
*/
|
||||
@ApiModelProperty(value = "客户类型:个人/企业/商户", name = "custType")
|
||||
@Excel(name = "客户类型(个人/企业/商户)", prompt = "必填", combo = "个人,企业,商户")
|
||||
private String custType;
|
||||
|
||||
/**
|
||||
* 客户号
|
||||
*/
|
||||
@ApiModelProperty(value = "客户号", name = "custId")
|
||||
@Excel(name = "客户号", prompt = "必填")
|
||||
private String custId;
|
||||
|
||||
/**
|
||||
* 客户姓名
|
||||
*/
|
||||
@ApiModelProperty(value = "客户姓名", name = "custName")
|
||||
@Excel(name = "客户姓名")
|
||||
private String custName;
|
||||
|
||||
/**
|
||||
* 客户身份证号
|
||||
*/
|
||||
@ApiModelProperty(value = "客户身份证号(个人客户必填)", name = "custIdc")
|
||||
@Excel(name = "客户身份证号(个人客户必填)")
|
||||
private String custIdc;
|
||||
|
||||
/**
|
||||
* 统信码
|
||||
*/
|
||||
@ApiModelProperty(value = "统信码(企业/商户客户必填)", name = "socialCreditCode")
|
||||
@Excel(name = "统信码(企业/商户客户必填)")
|
||||
private String socialCreditCode;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.ruoyi.group.domain.dto;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 客群查询DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
public class CustGroupQueryDTO implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 客群名称(模糊查询)
|
||||
*/
|
||||
@ApiModelProperty(value = "客群名称", name = "groupName")
|
||||
private String groupName;
|
||||
|
||||
/**
|
||||
* 客群模式:0=静态, 1=动态
|
||||
*/
|
||||
@ApiModelProperty(value = "客群模式", name = "groupMode")
|
||||
private String groupMode;
|
||||
|
||||
/**
|
||||
* 创建方式:1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格
|
||||
*/
|
||||
@ApiModelProperty(value = "创建方式", name = "createMode")
|
||||
private String createMode;
|
||||
|
||||
/**
|
||||
* 客群状态:0=正常, 1=已禁用
|
||||
*/
|
||||
@ApiModelProperty(value = "客群状态", name = "groupStatus")
|
||||
private String groupStatus;
|
||||
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
@ApiModelProperty(value = "页码", name = "pageNum")
|
||||
private Integer pageNum = 1;
|
||||
|
||||
/**
|
||||
* 每页大小
|
||||
*/
|
||||
@ApiModelProperty(value = "每页大小", name = "pageSize")
|
||||
private Integer pageSize = 10;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.ruoyi.group.domain.dto;
|
||||
|
||||
import com.ruoyi.group.domain.entity.CustGroup;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.validation.constraints.NotBlank;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 网格导入客群DTO
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
public class GridImportDTO {
|
||||
|
||||
/**
|
||||
* 客群信息
|
||||
*/
|
||||
@ApiModelProperty(value = "客群信息", name = "custGroup")
|
||||
@Valid
|
||||
@NotNull(message = "客群信息不能为空")
|
||||
private CustGroup custGroup;
|
||||
|
||||
/**
|
||||
* 网格类型:0=绩效网格, 1=地理网格, 2=绘制网格
|
||||
*/
|
||||
@ApiModelProperty(value = "网格类型:0=绩效网格, 1=地理网格, 2=绘制网格", name = "gridType")
|
||||
@NotBlank(message = "网格类型不能为空")
|
||||
private String gridType;
|
||||
|
||||
/**
|
||||
* 绩效网格业务类型:retail=零售, corporate=公司(gridType=0时必填)
|
||||
*/
|
||||
@ApiModelProperty(value = "绩效网格业务类型:retail=零售, corporate=公司", name = "cmpmBizType")
|
||||
private String cmpmBizType;
|
||||
|
||||
/**
|
||||
* 绩效网格客户经理柜员号列表(gridType=0时必填)
|
||||
*/
|
||||
@ApiModelProperty(value = "客户经理柜员号列表", name = "userNames")
|
||||
private List<String> userNames;
|
||||
|
||||
/**
|
||||
* 地理网格ID列表(gridType=1时必填)
|
||||
*/
|
||||
@ApiModelProperty(value = "地理网格ID列表", name = "regionGridIds")
|
||||
private List<Long> regionGridIds;
|
||||
|
||||
/**
|
||||
* 绘制网格ID列表(gridType=2时必填)
|
||||
*/
|
||||
@ApiModelProperty(value = "绘制网格ID列表", name = "drawGridIds")
|
||||
private List<Long> drawGridIds;
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
package com.ruoyi.group.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 客群实体
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
@TableName("ibs_cust_group")
|
||||
public class CustGroup {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@ApiModelProperty(value = "主键ID", name = "id")
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 客群名称
|
||||
*/
|
||||
@ApiModelProperty(value = "客群名称", name = "groupName")
|
||||
private String groupName;
|
||||
|
||||
/**
|
||||
* 客群模式:0=静态, 1=动态
|
||||
*/
|
||||
@ApiModelProperty(value = "客群模式:0=静态, 1=动态", name = "groupMode")
|
||||
private String groupMode;
|
||||
|
||||
/**
|
||||
* 创建方式:1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格
|
||||
*/
|
||||
@ApiModelProperty(value = "创建方式:1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格", name = "createMode")
|
||||
private String createMode;
|
||||
|
||||
/**
|
||||
* 柜员号
|
||||
*/
|
||||
@ApiModelProperty(value = "柜员号", name = "userName")
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 柜员名称
|
||||
*/
|
||||
@ApiModelProperty(value = "柜员名称", name = "nickName")
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 所属机构ID
|
||||
*/
|
||||
@ApiModelProperty(value = "所属机构ID", name = "deptId")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 是否开启共享:0=否, 1=是
|
||||
*/
|
||||
@ApiModelProperty(value = "是否开启共享:0=否, 1=是", name = "shareEnabled")
|
||||
private Integer shareEnabled;
|
||||
|
||||
/**
|
||||
* 可见部门ID列表(逗号分隔)
|
||||
*/
|
||||
@ApiModelProperty(value = "可见部门ID列表(逗号分隔)", name = "shareDeptIds")
|
||||
private String shareDeptIds;
|
||||
|
||||
/**
|
||||
* 共享部门ID列表(非表字段,用于接收前端传参)
|
||||
*/
|
||||
@ApiModelProperty(value = "共享部门ID列表", name = "shareDeptIdList")
|
||||
@TableField(exist = false)
|
||||
private List<Long> shareDeptIdList;
|
||||
|
||||
/**
|
||||
* 客群状态:0=正常, 1=已禁用
|
||||
*/
|
||||
@ApiModelProperty(value = "客群状态:0=正常, 1=已禁用", name = "groupStatus")
|
||||
private String groupStatus;
|
||||
|
||||
/**
|
||||
* 创建状态:0=创建中, 1=创建成功, 2=创建失败
|
||||
*/
|
||||
@ApiModelProperty(value = "创建状态:0=创建中, 1=创建成功, 2=创建失败", name = "createStatus")
|
||||
private String createStatus;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 删除标识:0=正常, 1=删除
|
||||
*/
|
||||
@TableLogic(value = "0", delval = "1")
|
||||
@TableField("del_flag")
|
||||
private Integer delFlag;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@ApiModelProperty(value = "备注", name = "remark")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 网格类型: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;
|
||||
|
||||
/**
|
||||
* 有效期截止时间
|
||||
*/
|
||||
@ApiModelProperty(value = "有效期截止时间", name = "validTime")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date validTime;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.ruoyi.group.domain.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 客群客户关联实体
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
@TableName("ibs_cust_group_member")
|
||||
public class CustGroupMember {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@ApiModelProperty(value = "主键ID", name = "id")
|
||||
@TableId(type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 客群ID
|
||||
*/
|
||||
@ApiModelProperty(value = "客群ID", name = "groupId")
|
||||
private Long groupId;
|
||||
|
||||
/**
|
||||
* 客户类型:0=个人, 1=商户, 2=企业
|
||||
*/
|
||||
@ApiModelProperty(value = "客户类型:0=个人, 1=商户, 2=企业", name = "custType")
|
||||
private String custType;
|
||||
|
||||
/**
|
||||
* 客户号
|
||||
*/
|
||||
@ApiModelProperty(value = "客户号", name = "custId")
|
||||
private String custId;
|
||||
|
||||
/**
|
||||
* 客户姓名(冗余)
|
||||
*/
|
||||
@ApiModelProperty(value = "客户姓名(冗余)", name = "custName")
|
||||
private String custName;
|
||||
|
||||
/**
|
||||
* 客户身份证号
|
||||
*/
|
||||
@ApiModelProperty(value = "客户身份证号", name = "custIdc")
|
||||
private String custIdc;
|
||||
|
||||
/**
|
||||
* 统信码(商户/企业有)
|
||||
*/
|
||||
@ApiModelProperty(value = "统信码(商户/企业有)", name = "socialCreditCode")
|
||||
private String socialCreditCode;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 删除标识:0=正常, 1=删除
|
||||
*/
|
||||
@TableLogic(value = "0", delval = "1")
|
||||
@TableField("del_flag")
|
||||
private Integer delFlag;
|
||||
|
||||
/**
|
||||
* 手动移除标识:0=否, 1=是(被手动移除的客户不会被定时任务重新导入)
|
||||
*/
|
||||
@ApiModelProperty(value = "手动移除标识:0=否, 1=是", name = "manualRemove")
|
||||
@TableField("manual_remove")
|
||||
private Integer manualRemove;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.ruoyi.group.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 客群客户VO
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
public class CustGroupMemberVO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@ApiModelProperty(value = "主键ID", name = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 客群ID
|
||||
*/
|
||||
@ApiModelProperty(value = "客群ID", name = "groupId")
|
||||
private Long groupId;
|
||||
|
||||
/**
|
||||
* 客户类型:0=个人, 1=商户, 2=企业
|
||||
*/
|
||||
@ApiModelProperty(value = "客户类型:0=个人, 1=商户, 2=企业", name = "custType")
|
||||
private String custType;
|
||||
|
||||
/**
|
||||
* 客户号
|
||||
*/
|
||||
@ApiModelProperty(value = "客户号", name = "custId")
|
||||
private String custId;
|
||||
|
||||
/**
|
||||
* 客户姓名
|
||||
*/
|
||||
@ApiModelProperty(value = "客户姓名", name = "custName")
|
||||
private String custName;
|
||||
|
||||
/**
|
||||
* 客户身份证号
|
||||
*/
|
||||
@ApiModelProperty(value = "客户身份证号", name = "custIdc")
|
||||
private String custIdc;
|
||||
|
||||
/**
|
||||
* 统信码(商户/企业有)
|
||||
*/
|
||||
@ApiModelProperty(value = "统信码(商户/企业有)", name = "socialCreditCode")
|
||||
private String socialCreditCode;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@ApiModelProperty(value = "创建时间", name = "createTime")
|
||||
private Date createTime;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.ruoyi.group.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 客群VO
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Data
|
||||
public class CustGroupVO {
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@ApiModelProperty(value = "主键ID", name = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 客群名称
|
||||
*/
|
||||
@ApiModelProperty(value = "客群名称", name = "groupName")
|
||||
private String groupName;
|
||||
|
||||
/**
|
||||
* 客群模式:0=静态, 1=动态
|
||||
*/
|
||||
@ApiModelProperty(value = "客群模式:0=静态, 1=动态", name = "groupMode")
|
||||
private String groupMode;
|
||||
|
||||
/**
|
||||
* 创建方式:1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格
|
||||
*/
|
||||
@ApiModelProperty(value = "创建方式:1=模板导入, 2=绩效网格, 3=地理网格, 4=自定义网格", name = "createMode")
|
||||
private String createMode;
|
||||
|
||||
/**
|
||||
* 柜员号
|
||||
*/
|
||||
@ApiModelProperty(value = "柜员号", name = "userName")
|
||||
private String userName;
|
||||
|
||||
/**
|
||||
* 柜员名称
|
||||
*/
|
||||
@ApiModelProperty(value = "柜员名称", name = "nickName")
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 所属机构ID
|
||||
*/
|
||||
@ApiModelProperty(value = "所属机构ID", name = "deptId")
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 是否开启共享:0=否, 1=是
|
||||
*/
|
||||
@ApiModelProperty(value = "是否开启共享:0=否, 1=是", name = "shareEnabled")
|
||||
private Integer shareEnabled;
|
||||
|
||||
/**
|
||||
* 可见部门ID列表
|
||||
*/
|
||||
@ApiModelProperty(value = "可见部门ID列表", name = "shareDeptIds")
|
||||
private List<Long> shareDeptIds;
|
||||
|
||||
/**
|
||||
* 客群状态:0=正常, 1=已禁用
|
||||
*/
|
||||
@ApiModelProperty(value = "客群状态:0=正常, 1=已禁用", name = "groupStatus")
|
||||
private String groupStatus;
|
||||
|
||||
/**
|
||||
* 客户数量
|
||||
*/
|
||||
@ApiModelProperty(value = "客户数量", name = "custCount")
|
||||
private Integer custCount;
|
||||
|
||||
/**
|
||||
* 创建者
|
||||
*/
|
||||
@ApiModelProperty(value = "创建者", name = "createBy")
|
||||
private String createBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@ApiModelProperty(value = "创建时间", name = "createTime")
|
||||
private Date createTime;
|
||||
|
||||
/**
|
||||
* 更新者
|
||||
*/
|
||||
@ApiModelProperty(value = "更新者", name = "updateBy")
|
||||
private String updateBy;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@ApiModelProperty(value = "更新时间", name = "updateTime")
|
||||
private Date updateTime;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
@ApiModelProperty(value = "备注", name = "remark")
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 客户列表
|
||||
*/
|
||||
@ApiModelProperty(value = "客户列表", name = "custList")
|
||||
private List<CustGroupMemberVO> custList;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.ruoyi.group.mapper;
|
||||
|
||||
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.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 客群Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface CustGroupMapper extends BaseMapper<CustGroup> {
|
||||
|
||||
/**
|
||||
* 查询客群数量(根据部门ID和共享设置)
|
||||
*
|
||||
* @param deptId 部门ID
|
||||
* @param userName 用户名
|
||||
* @return 数量
|
||||
*/
|
||||
// Long countByDeptOrUser(@Param("deptId") Long deptId, @Param("userName") Long userName);
|
||||
|
||||
/**
|
||||
* 查询客群列表
|
||||
*
|
||||
* @param dto 查询条件
|
||||
* @return 客群VO列表
|
||||
*/
|
||||
List<CustGroupVO> selectCustGroupList(@Param("dto") CustGroupQueryDTO dto);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.ruoyi.group.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.group.domain.entity.CustGroupMember;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 客群客户关联Mapper接口
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface CustGroupMemberMapper extends BaseMapper<CustGroupMember> {
|
||||
|
||||
/**
|
||||
* 查询客群客户数量
|
||||
*
|
||||
* @param groupId 客群ID
|
||||
* @return 数量
|
||||
*/
|
||||
Long countByGroupId(@Param("groupId") Long groupId);
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.ruoyi.group.service;
|
||||
|
||||
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.vo.CustGroupVO;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 客群Service接口
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface ICustGroupService {
|
||||
|
||||
/**
|
||||
* 查询客群列表
|
||||
*
|
||||
* @param dto 查询条件
|
||||
* @return 客群VO列表
|
||||
*/
|
||||
List<CustGroupVO> listCustGroup(CustGroupQueryDTO dto);
|
||||
|
||||
/**
|
||||
* 异步创建客群(模板导入)
|
||||
*
|
||||
* @param custGroup 客群实体
|
||||
* @param file Excel文件
|
||||
* @return 客群ID
|
||||
*/
|
||||
String createCustGroupByTemplate(CustGroup custGroup, MultipartFile file);
|
||||
|
||||
/**
|
||||
* 异步创建客群(网格导入)
|
||||
*
|
||||
* @param gridImportDTO 网格导入条件
|
||||
* @return 客群ID
|
||||
*/
|
||||
String createCustGroupByGrid(GridImportDTO gridImportDTO);
|
||||
|
||||
/**
|
||||
* 更新客群(网格导入)
|
||||
*
|
||||
* @param gridImportDTO 网格导入条件
|
||||
* @return 结果消息
|
||||
*/
|
||||
String updateCustGroupByGrid(GridImportDTO gridImportDTO);
|
||||
|
||||
/**
|
||||
* 更新客群(模板导入)
|
||||
*
|
||||
* @param custGroup 客群实体
|
||||
* @param file Excel文件
|
||||
* @return 结果消息
|
||||
*/
|
||||
String updateCustGroupByTemplate(CustGroup custGroup, MultipartFile file);
|
||||
|
||||
/**
|
||||
* 更新客群
|
||||
*
|
||||
* @param custGroup 客群实体
|
||||
* @return 结果消息
|
||||
*/
|
||||
String updateCustGroup(CustGroup custGroup);
|
||||
|
||||
/**
|
||||
* 删除客群
|
||||
*
|
||||
* @param idList 客群ID列表
|
||||
* @return 结果消息
|
||||
*/
|
||||
String deleteCustGroup(List<Long> idList);
|
||||
|
||||
/**
|
||||
* 获取客群详情
|
||||
*
|
||||
* @param id 客群ID
|
||||
* @return 客群VO
|
||||
*/
|
||||
CustGroupVO getCustGroup(Long id);
|
||||
|
||||
/**
|
||||
* 检查客群名称是否存在
|
||||
*
|
||||
* @param groupName 客群名称
|
||||
* @return true=存在, false=不存在
|
||||
*/
|
||||
boolean checkGroupNameExist(String groupName);
|
||||
|
||||
/**
|
||||
* 查询客群创建状态
|
||||
*
|
||||
* @param id 客群ID
|
||||
* @return 创建状态:0=创建中, 1=创建成功, 2=创建失败
|
||||
*/
|
||||
String getCreateStatus(Long id);
|
||||
|
||||
/**
|
||||
* 手动移除客群客户
|
||||
*
|
||||
* @param groupId 客群ID
|
||||
* @param memberIds 客群成员ID列表
|
||||
* @return 结果消息
|
||||
*/
|
||||
String removeMembers(Long groupId, List<Long> memberIds);
|
||||
|
||||
/**
|
||||
* 更新动态客群(定时任务调用)
|
||||
* 根据原始导入条件重新查询并更新客户列表
|
||||
*/
|
||||
void updateDynamicCustGroups();
|
||||
|
||||
/**
|
||||
* 检查并禁用过期客群(定时任务调用)
|
||||
*/
|
||||
void checkAndDisableExpiredGroups();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,817 @@
|
||||
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.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 lombok.extern.slf4j.Slf4j;
|
||||
import lombok.val;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 客群Service实现
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CustGroupServiceImpl implements ICustGroupService {
|
||||
|
||||
@Resource
|
||||
private CustGroupMapper custGroupMapper;
|
||||
|
||||
@Resource
|
||||
private CustGroupMemberMapper custGroupMemberMapper;
|
||||
|
||||
@Resource(name = "excelImportExecutor")
|
||||
private ExecutorService executorService;
|
||||
|
||||
@Resource
|
||||
private GridCmpmMapper gridCmpmMapper;
|
||||
|
||||
@Resource
|
||||
private RegionCustUserMapper regionCustUserMapper;
|
||||
|
||||
@Resource
|
||||
private RegionGridMapper regionGridMapper;
|
||||
|
||||
@Resource
|
||||
private RegionGridListService regionGridListService;
|
||||
|
||||
@Resource
|
||||
private DrawShapeCustMapper drawShapeCustMapper;
|
||||
|
||||
@Resource
|
||||
private DrawGridShapeRelateMapper drawGridShapeRelateMapper;
|
||||
|
||||
@Resource
|
||||
private SysDeptMapper sysDeptMapper;
|
||||
|
||||
@Override
|
||||
public List<CustGroupVO> listCustGroup(CustGroupQueryDTO dto) {
|
||||
return custGroupMapper.selectCustGroupList(dto);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String createCustGroupByTemplate(CustGroup custGroup, MultipartFile file) {
|
||||
// 检查客群名称是否存在
|
||||
if (checkGroupNameExist(custGroup.getGroupName())) {
|
||||
throw new ServiceException("客群名称已存在");
|
||||
}
|
||||
// 获取当前用户信息
|
||||
SysUser user = SecurityUtils.getLoginUser().getUser();
|
||||
custGroup.setUserName(user.getUserName());
|
||||
custGroup.setNickName(user.getNickName());
|
||||
// 设置默认值
|
||||
custGroup.setGroupMode("0"); // 静态
|
||||
custGroup.setCreateMode("1"); // 模板导入
|
||||
if (StringUtils.isEmpty(custGroup.getGroupStatus())) {
|
||||
custGroup.setGroupStatus("0");
|
||||
}
|
||||
// 设置创建状态为创建中
|
||||
custGroup.setCreateStatus("0");
|
||||
// 插入客群
|
||||
custGroupMapper.insert(custGroup);
|
||||
// 获取当前用户部门编码(异步线程中无法获取)
|
||||
String headId = SecurityUtils.getHeadId();
|
||||
// 异步导入客户
|
||||
executorService.submit(() -> doImportCustGroupByTemplate(custGroup, file, headId));
|
||||
return String.valueOf(custGroup.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String createCustGroupByGrid(GridImportDTO gridImportDTO) {
|
||||
CustGroup custGroup = gridImportDTO.getCustGroup();
|
||||
// 检查客群名称是否存在
|
||||
if (checkGroupNameExist(custGroup.getGroupName())) {
|
||||
throw new ServiceException("客群名称已存在");
|
||||
}
|
||||
// 获取当前用户信息
|
||||
SysUser user = SecurityUtils.getLoginUser().getUser();
|
||||
custGroup.setUserName(user.getUserName());
|
||||
custGroup.setNickName(user.getNickName());
|
||||
// 默认状态为正常
|
||||
if (StringUtils.isEmpty(custGroup.getGroupStatus())) {
|
||||
custGroup.setGroupStatus("0");
|
||||
}
|
||||
// 设置创建模式和状态
|
||||
custGroup.setGroupMode(custGroup.getGroupMode());
|
||||
// 根据网格类型设置创建方式:0=模板导入, 2=绩效网格, 3=地理网格, 4=绘制网格
|
||||
String gridType = gridImportDTO.getGridType();
|
||||
if ("0".equals(gridType)) {
|
||||
custGroup.setCreateMode("2"); // 绩效网格
|
||||
} else if ("1".equals(gridType)) {
|
||||
custGroup.setCreateMode("3"); // 地理网格
|
||||
} else if ("2".equals(gridType)) {
|
||||
custGroup.setCreateMode("4"); // 绘制网格
|
||||
} else {
|
||||
throw new ServiceException("无效的网格类型");
|
||||
}
|
||||
|
||||
// 保存网格导入条件(用于动态客群后续更新)
|
||||
custGroup.setGridType(gridImportDTO.getGridType());
|
||||
if ("0".equals(gridType)) {
|
||||
custGroup.setCmpmBizType(gridImportDTO.getCmpmBizType());
|
||||
if (gridImportDTO.getUserNames() != null && !gridImportDTO.getUserNames().isEmpty()) {
|
||||
custGroup.setGridUserNames(String.join(",", gridImportDTO.getUserNames()));
|
||||
}
|
||||
} else if ("1".equals(gridType)) {
|
||||
if (gridImportDTO.getRegionGridIds() != null && !gridImportDTO.getRegionGridIds().isEmpty()) {
|
||||
custGroup.setRegionGridIds(gridImportDTO.getRegionGridIds().stream()
|
||||
.map(String::valueOf).collect(Collectors.joining(",")));
|
||||
}
|
||||
} else if ("2".equals(gridType)) {
|
||||
if (gridImportDTO.getDrawGridIds() != null && !gridImportDTO.getDrawGridIds().isEmpty()) {
|
||||
custGroup.setDrawGridIds(gridImportDTO.getDrawGridIds().stream()
|
||||
.map(String::valueOf).collect(Collectors.joining(",")));
|
||||
}
|
||||
}
|
||||
|
||||
// 设置创建状态为创建中
|
||||
custGroup.setCreateStatus("0");
|
||||
// 插入客群
|
||||
custGroupMapper.insert(custGroup);
|
||||
// 重新设置回DTO(确保异步线程能获取到正确的ID和状态)
|
||||
gridImportDTO.setCustGroup(custGroup);
|
||||
// 获取当前用户部门编码(异步线程中无法获取)
|
||||
String headId = SecurityUtils.getHeadId();
|
||||
// 异步导入客户
|
||||
executorService.submit(() -> doImportCustGroupByGrid(gridImportDTO, headId));
|
||||
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) {
|
||||
CustGroup custGroup = gridImportDTO.getCustGroup();
|
||||
// 检查客群是否存在
|
||||
CustGroup existGroup = custGroupMapper.selectById(custGroup.getId());
|
||||
if (existGroup == null) {
|
||||
throw new ServiceException("客群不存在");
|
||||
}
|
||||
// 更新客群基本信息
|
||||
if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) {
|
||||
throw new ServiceException("客群名称已存在");
|
||||
}
|
||||
// 重新查询数据库,获取最新状态
|
||||
CustGroup latestGroup = custGroupMapper.selectById(custGroup.getId());
|
||||
latestGroup.setGroupName(custGroup.getGroupName());
|
||||
latestGroup.setGroupMode(custGroup.getGroupMode());
|
||||
latestGroup.setRemark(custGroup.getRemark());
|
||||
latestGroup.setValidTime(custGroup.getValidTime());
|
||||
latestGroup.setShareEnabled(custGroup.getShareEnabled());
|
||||
latestGroup.setShareDeptIds(custGroup.getShareDeptIds());
|
||||
// 更新数据库
|
||||
custGroupMapper.updateById(latestGroup);
|
||||
// 重新设置回DTO(确保异步线程能获取到正确的ID和状态)
|
||||
gridImportDTO.setCustGroup(latestGroup);
|
||||
// 获取当前用户部门编码(异步线程中无法获取)
|
||||
String headId = SecurityUtils.getHeadId();
|
||||
// 异步导入客户
|
||||
executorService.submit(() -> doImportCustGroupByGrid(gridImportDTO, headId));
|
||||
return "客群更新中";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String updateCustGroupByTemplate(CustGroup custGroup, MultipartFile file) {
|
||||
// 检查客群是否存在
|
||||
CustGroup existGroup = custGroupMapper.selectById(custGroup.getId());
|
||||
if (existGroup == null) {
|
||||
throw new ServiceException("客群不存在");
|
||||
}
|
||||
// 更新客群基本信息
|
||||
if (!existGroup.getGroupName().equals(custGroup.getGroupName()) && checkGroupNameExist(custGroup.getGroupName())) {
|
||||
throw new ServiceException("客群名称已存在");
|
||||
}
|
||||
// 重新查询数据库,获取最新状态
|
||||
CustGroup latestGroup = custGroupMapper.selectById(custGroup.getId());
|
||||
latestGroup.setGroupName(custGroup.getGroupName());
|
||||
latestGroup.setGroupMode(custGroup.getGroupMode());
|
||||
latestGroup.setRemark(custGroup.getRemark());
|
||||
latestGroup.setValidTime(custGroup.getValidTime());
|
||||
latestGroup.setShareEnabled(custGroup.getShareEnabled());
|
||||
latestGroup.setShareDeptIds(custGroup.getShareDeptIds());
|
||||
// 更新数据库
|
||||
custGroupMapper.updateById(latestGroup);
|
||||
// 获取当前用户部门编码(异步线程中无法获取)
|
||||
String headId = SecurityUtils.getHeadId();
|
||||
// 异步导入客户(使用最新状态的对象)
|
||||
executorService.submit(() -> doImportCustGroupByTemplate(latestGroup, file, headId));
|
||||
return "客群更新中";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String deleteCustGroup(List<Long> idList) {
|
||||
if (idList == null || idList.isEmpty()) {
|
||||
throw new ServiceException("请选择要删除的客群");
|
||||
}
|
||||
for (Long id : idList) {
|
||||
// 删除客群客户关联
|
||||
LambdaQueryWrapper<CustGroupMember> memberWrapper = new LambdaQueryWrapper<>();
|
||||
memberWrapper.eq(CustGroupMember::getGroupId, id);
|
||||
custGroupMemberMapper.delete(memberWrapper);
|
||||
// 删除客群
|
||||
custGroupMapper.deleteById(id);
|
||||
}
|
||||
return "客群删除成功";
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String removeMembers(Long groupId, List<Long> 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<Long> deptIds = new ArrayList<>();
|
||||
for (String idStr : custGroup.getShareDeptIds().split(",")) {
|
||||
if (StringUtils.isNotEmpty(idStr)) {
|
||||
deptIds.add(Long.valueOf(idStr));
|
||||
}
|
||||
}
|
||||
vo.setShareDeptIds(deptIds);
|
||||
}
|
||||
// 查询客户列表
|
||||
LambdaQueryWrapper<CustGroupMember> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(CustGroupMember::getGroupId, id);
|
||||
List<CustGroupMember> memberList = custGroupMemberMapper.selectList(wrapper);
|
||||
List<CustGroupMemberVO> 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<CustGroup> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(CustGroup::getGroupName, groupName);
|
||||
return custGroupMapper.selectCount(wrapper) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCreateStatus(Long id) {
|
||||
CustGroup custGroup = custGroupMapper.selectById(id);
|
||||
if (custGroup == null) {
|
||||
throw new ServiceException("客群不存在");
|
||||
}
|
||||
return custGroup.getCreateStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDynamicCustGroups() {
|
||||
log.info("开始更新动态客群...");
|
||||
|
||||
// 查询所有动态客群
|
||||
LambdaQueryWrapper<CustGroup> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(CustGroup::getGroupMode, "1"); // 1=动态
|
||||
wrapper.eq(CustGroup::getGroupStatus, "0"); // 0=正常
|
||||
wrapper.eq(CustGroup::getCreateStatus, "1"); // 1=创建成功
|
||||
List<CustGroup> dynamicGroups = custGroupMapper.selectList(wrapper);
|
||||
|
||||
if (dynamicGroups.isEmpty()) {
|
||||
log.info("没有需要更新的动态客群");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("找到 {} 个动态客群需要更新", dynamicGroups.size());
|
||||
Date now = new Date();
|
||||
|
||||
for (CustGroup custGroup : dynamicGroups) {
|
||||
// 检查有效期,过期的客群跳过更新
|
||||
if (custGroup.getValidTime() != null && custGroup.getValidTime().before(now)) {
|
||||
log.info("动态客群已过期,跳过更新,客群ID:{},客群名称:{},有效期:{}",
|
||||
custGroup.getId(), custGroup.getGroupName(), custGroup.getValidTime());
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
updateDynamicCustGroup(custGroup);
|
||||
log.info("动态客群更新成功,客群ID:{},客群名称:{}", custGroup.getId(), custGroup.getGroupName());
|
||||
} catch (Exception e) {
|
||||
log.error("动态客群更新失败,客群ID:{},客群名称:{}", custGroup.getId(), custGroup.getGroupName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("动态客群更新完成,总计:{},成功:{},失败:{}",
|
||||
dynamicGroups.size(),
|
||||
dynamicGroups.stream().filter(g -> {
|
||||
// 假设更新成功的状态设置
|
||||
LambdaQueryWrapper<CustGroup> w = new LambdaQueryWrapper<>();
|
||||
w.eq(CustGroup::getId, g.getId());
|
||||
// 这里简单统计,实际可以通过更精确的方式
|
||||
return true;
|
||||
}).count(),
|
||||
0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkAndDisableExpiredGroups() {
|
||||
log.info("开始检查并禁用过期客群...");
|
||||
|
||||
Date now = new Date();
|
||||
|
||||
// 查询所有正常状态且有过期时间的客群
|
||||
LambdaQueryWrapper<CustGroup> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(CustGroup::getGroupStatus, "0"); // 0=正常
|
||||
wrapper.isNotNull(CustGroup::getValidTime);
|
||||
wrapper.le(CustGroup::getValidTime, now); // validTime <= now
|
||||
List<CustGroup> expiredGroups = custGroupMapper.selectList(wrapper);
|
||||
|
||||
if (expiredGroups.isEmpty()) {
|
||||
log.info("没有需要禁用的过期客群");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("找到 {} 个过期客群需要禁用", expiredGroups.size());
|
||||
|
||||
for (CustGroup custGroup : expiredGroups) {
|
||||
try {
|
||||
custGroup.setGroupStatus("1"); // 1=已禁用
|
||||
custGroupMapper.updateById(custGroup);
|
||||
log.info("客群已禁用,客群ID:{},客群名称:{},有效期:{}",
|
||||
custGroup.getId(), custGroup.getGroupName(), custGroup.getValidTime());
|
||||
} catch (Exception e) {
|
||||
log.error("禁用过期客群失败,客群ID:{},客群名称:{}", custGroup.getId(), custGroup.getGroupName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("过期客群禁用完成,总计:{}", expiredGroups.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新单个动态客群(增量更新方式)
|
||||
*/
|
||||
private void updateDynamicCustGroup(CustGroup custGroup) {
|
||||
// 从客群部门获取 headId(定时任务中无登录用户上下文)
|
||||
// headId 是 deptId 的前三位
|
||||
String headId = "";
|
||||
if (custGroup.getDeptId() != null) {
|
||||
String deptIdStr = String.valueOf(custGroup.getDeptId());
|
||||
headId = deptIdStr.substring(0, 3);
|
||||
}
|
||||
|
||||
// 查询现有客户(排除手动移除的客户,只查询正常状态的客户)
|
||||
LambdaQueryWrapper<CustGroupMember> existWrapper = new LambdaQueryWrapper<>();
|
||||
existWrapper.eq(CustGroupMember::getGroupId, custGroup.getId())
|
||||
.eq(CustGroupMember::getDelFlag, 0)
|
||||
.eq(CustGroupMember::getManualRemove, 0)
|
||||
.select(CustGroupMember::getCustId, CustGroupMember::getCustType);
|
||||
List<CustGroupMember> existMembers = custGroupMemberMapper.selectList(existWrapper);
|
||||
Set<String> existKeySet = existMembers.stream()
|
||||
.map(m -> m.getCustId() + "_" + m.getCustType())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 构造 GridImportDTO 复用现有导入方法
|
||||
GridImportDTO gridImportDTO = new GridImportDTO();
|
||||
gridImportDTO.setCustGroup(custGroup);
|
||||
gridImportDTO.setGridType(custGroup.getGridType());
|
||||
gridImportDTO.setCmpmBizType(custGroup.getCmpmBizType());
|
||||
|
||||
// 根据网格类型设置参数并查询
|
||||
List<CustGroupMember> newMemberList = new ArrayList<>();
|
||||
String gridType = custGroup.getGridType();
|
||||
if ("0".equals(gridType)) {
|
||||
// 绩效网格
|
||||
if (StringUtils.isNotEmpty(custGroup.getGridUserNames())) {
|
||||
gridImportDTO.setUserNames(Arrays.asList(custGroup.getGridUserNames().split(",")));
|
||||
}
|
||||
newMemberList.addAll(importFromCmpmGrid(custGroup, gridImportDTO, headId));
|
||||
} else if ("1".equals(gridType)) {
|
||||
// 地理网格
|
||||
if (StringUtils.isNotEmpty(custGroup.getRegionGridIds())) {
|
||||
gridImportDTO.setRegionGridIds(Arrays.stream(custGroup.getRegionGridIds().split(","))
|
||||
.map(Long::valueOf).collect(Collectors.toList()));
|
||||
}
|
||||
newMemberList.addAll(importFromRegionGrid(custGroup, gridImportDTO));
|
||||
} 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));
|
||||
}
|
||||
|
||||
// 计算差异
|
||||
Set<String> newKeySet = newMemberList.stream()
|
||||
.map(m -> m.getCustId() + "_" + m.getCustType())
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// 需要删除的客户:存在于旧列表但不存在于新列表
|
||||
List<String> toDeleteKeys = new ArrayList<>(existKeySet);
|
||||
toDeleteKeys.removeAll(newKeySet);
|
||||
// 需要新增的客户:存在于新列表但不存在于旧列表
|
||||
List<CustGroupMember> toAddMembers = newMemberList.stream()
|
||||
.filter(m -> !existKeySet.contains(m.getCustId() + "_" + m.getCustType()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 删除不再存在的客户
|
||||
if (!toDeleteKeys.isEmpty()) {
|
||||
for (String key : toDeleteKeys) {
|
||||
String[] parts = key.split("_");
|
||||
LambdaQueryWrapper<CustGroupMember> deleteWrapper = new LambdaQueryWrapper<>();
|
||||
deleteWrapper.eq(CustGroupMember::getGroupId, custGroup.getId())
|
||||
.eq(CustGroupMember::getCustId, parts[0])
|
||||
.eq(CustGroupMember::getCustType, parts[1]);
|
||||
custGroupMemberMapper.delete(deleteWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
// 插入新增客户
|
||||
int addCount = 0;
|
||||
for (CustGroupMember member : toAddMembers) {
|
||||
try {
|
||||
custGroupMemberMapper.insert(member);
|
||||
addCount++;
|
||||
} catch (org.springframework.dao.DuplicateKeyException e) {
|
||||
log.debug("客户已存在,跳过:groupId={}, custId={}", member.getGroupId(), member.getCustId());
|
||||
}
|
||||
}
|
||||
|
||||
log.info("客群客户更新完成,客群ID:{},原数量:{},新数量:{},新增:{},删除:{}",
|
||||
custGroup.getId(), existMembers.size(), newMemberList.size(), addCount, toDeleteKeys.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步导入客户
|
||||
*/
|
||||
private void doImportCustGroupByTemplate(CustGroup custGroup, MultipartFile file, String headId) {
|
||||
try {
|
||||
// 解析Excel
|
||||
List<CustGroupMemberTemplate> templateList = EasyExcel.read(file.getInputStream())
|
||||
.head(CustGroupMemberTemplate.class)
|
||||
.sheet()
|
||||
.doReadSync();
|
||||
if (templateList.isEmpty()) {
|
||||
throw new ServiceException("导入列表为空");
|
||||
}
|
||||
// 校验和去重
|
||||
Set<String> uniqueCustIds = new HashSet<>();
|
||||
List<CustGroupMember> memberList = new ArrayList<>();
|
||||
for (CustGroupMemberTemplate template : templateList) {
|
||||
// 校验客户类型
|
||||
String custType = template.getCustType();
|
||||
if (StringUtils.isEmpty(custType)) {
|
||||
throw new ServiceException("客户类型不能为空");
|
||||
}
|
||||
// 转换客户类型值
|
||||
String custTypeValue;
|
||||
switch (custType) {
|
||||
case "个人":
|
||||
custTypeValue = "0";
|
||||
break;
|
||||
case "企业":
|
||||
custTypeValue = "2";
|
||||
break;
|
||||
case "商户":
|
||||
custTypeValue = "1";
|
||||
break;
|
||||
default:
|
||||
throw new ServiceException("客户类型填写错误,只能填写:个人、企业、商户");
|
||||
}
|
||||
// 客户号不能为空
|
||||
if (StringUtils.isEmpty(template.getCustId())) {
|
||||
throw new ServiceException("客户号不能为空");
|
||||
}
|
||||
// 检查重复
|
||||
if (!uniqueCustIds.add(template.getCustId())) {
|
||||
continue; // 跳过重复客户
|
||||
}
|
||||
// 创建客户成员
|
||||
CustGroupMember member = new CustGroupMember();
|
||||
member.setGroupId(custGroup.getId());
|
||||
member.setCustType(custTypeValue);
|
||||
member.setCustId(template.getCustId());
|
||||
member.setCustName(template.getCustName());
|
||||
member.setCustIdc(template.getCustIdc());
|
||||
member.setSocialCreditCode(template.getSocialCreditCode());
|
||||
member.setCreateTime(new Date());
|
||||
// 个人客户必须有身份证号
|
||||
if ("0".equals(custTypeValue) && StringUtils.isEmpty(member.getCustIdc())) {
|
||||
throw new ServiceException("个人客户[" + template.getCustId() + "]身份证号不能为空");
|
||||
}
|
||||
// 企业/商户客户必须有统信码
|
||||
if (("1".equals(custTypeValue) || "2".equals(custTypeValue))
|
||||
&& StringUtils.isEmpty(member.getSocialCreditCode())) {
|
||||
throw new ServiceException("企业/商户客户[" + template.getCustId() + "]统信码不能为空");
|
||||
}
|
||||
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<CustGroupMember> batchList = memberList.subList(i, endIndex);
|
||||
for (CustGroupMember member : batchList) {
|
||||
try {
|
||||
custGroupMemberMapper.insert(member);
|
||||
successCount++;
|
||||
} catch (DuplicateKeyException e) {
|
||||
// 客户已存在,检查是否是被手动移除的
|
||||
LambdaQueryWrapper<CustGroupMember> 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);
|
||||
} catch (Exception e) {
|
||||
// 更新创建状态为失败
|
||||
custGroup.setCreateStatus("2");
|
||||
custGroupMapper.updateById(custGroup);
|
||||
log.error("客群客户导入失败,客群ID:{}", custGroup.getId(), e);
|
||||
throw new ServiceException("客群客户导入失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步导入客户(网格方式)
|
||||
*/
|
||||
private void doImportCustGroupByGrid(GridImportDTO gridImportDTO, String headId) {
|
||||
CustGroup custGroup = gridImportDTO.getCustGroup();
|
||||
try {
|
||||
List<CustGroupMember> memberList = new ArrayList<>();
|
||||
String gridType = gridImportDTO.getGridType();
|
||||
// 根据网格类型查询客户
|
||||
if ("0".equals(gridType)) {
|
||||
// 绩效网格
|
||||
memberList.addAll(importFromCmpmGrid(custGroup, gridImportDTO, headId));
|
||||
} else if ("1".equals(gridType)) {
|
||||
// 地理网格
|
||||
memberList.addAll(importFromRegionGrid(custGroup, gridImportDTO));
|
||||
} else if ("2".equals(gridType)) {
|
||||
// 绘制网格
|
||||
memberList.addAll(importFromDrawGrid(custGroup, gridImportDTO));
|
||||
}
|
||||
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<CustGroupMember> batchList = memberList.subList(i, endIndex);
|
||||
for (CustGroupMember member : batchList) {
|
||||
try {
|
||||
custGroupMemberMapper.insert(member);
|
||||
successCount++;
|
||||
} catch (DuplicateKeyException e) {
|
||||
// 客户已存在,检查是否是被手动移除的
|
||||
LambdaQueryWrapper<CustGroupMember> 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);
|
||||
} catch (Exception e) {
|
||||
// 更新创建状态为失败
|
||||
custGroup.setCreateStatus("2");
|
||||
custGroupMapper.updateById(custGroup);
|
||||
log.error("客群客户导入失败,客群ID:{}", custGroup.getId(), e);
|
||||
throw new ServiceException("客群客户导入失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从绩效网格导入客户
|
||||
*/
|
||||
private List<CustGroupMember> importFromCmpmGrid(CustGroup custGroup, GridImportDTO gridImportDTO, String headId) {
|
||||
List<CustGroupMember> memberList = new ArrayList<>();
|
||||
// 确定要查询的网格类型
|
||||
List<String> gridTypes = new ArrayList<>();
|
||||
String cmpmBizType = gridImportDTO.getCmpmBizType();
|
||||
if ("retail".equals(cmpmBizType)) {
|
||||
gridTypes.add("retail");
|
||||
} else if ("corporate".equals(cmpmBizType)) {
|
||||
gridTypes.add("corporate");
|
||||
gridTypes.add("corporate_account");
|
||||
} else {
|
||||
throw new ServiceException("请选择绩效网格业务类型(零售/公司)");
|
||||
}
|
||||
// 查询客户
|
||||
for (String userName : gridImportDTO.getUserNames()) {
|
||||
for (String gridType : gridTypes) {
|
||||
List<GridCmpm> cmpmList = gridCmpmMapper.getGridCmpmByUserName(userName, headId, gridType);
|
||||
for (GridCmpm cmpm : cmpmList) {
|
||||
CustGroupMember member = new CustGroupMember();
|
||||
member.setGroupId(custGroup.getId());
|
||||
member.setCustId(cmpm.getCustId());
|
||||
member.setCustName(cmpm.getCustName());
|
||||
member.setCustType(cmpm.getCustType());
|
||||
member.setCustIdc(cmpm.getCustIdc());
|
||||
member.setSocialCreditCode(cmpm.getUsci());
|
||||
member.setCreateTime(new Date());
|
||||
memberList.add(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
return memberList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从地理网格导入客户
|
||||
*/
|
||||
private List<CustGroupMember> importFromRegionGrid(CustGroup custGroup, GridImportDTO gridImportDTO) {
|
||||
List<CustGroupMember> memberList = new ArrayList<>();
|
||||
// 查询地理网格获取编码
|
||||
if (gridImportDTO.getRegionGridIds() == null || gridImportDTO.getRegionGridIds().isEmpty()) {
|
||||
throw new ServiceException("请选择地理网格");
|
||||
}
|
||||
List<RegionGrid> regionGrids = regionGridMapper.selectBatchIds(gridImportDTO.getRegionGridIds());
|
||||
// 使用 selectAllCustFromGrid 方法查询所有客户(不限制客户类型)
|
||||
for (RegionGrid regionGrid : regionGrids) {
|
||||
List<RegionCustUser> 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);
|
||||
}
|
||||
}
|
||||
return memberList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从绘制网格导入客户
|
||||
*/
|
||||
private List<CustGroupMember> importFromDrawGrid(CustGroup custGroup, GridImportDTO gridImportDTO) {
|
||||
List<CustGroupMember> memberList = new ArrayList<>();
|
||||
if (gridImportDTO.getDrawGridIds() == null || gridImportDTO.getDrawGridIds().isEmpty()) {
|
||||
throw new ServiceException("请选择绘制网格");
|
||||
}
|
||||
// 查询绘制网格关联的图形ID
|
||||
for (Long gridId : gridImportDTO.getDrawGridIds()) {
|
||||
LambdaQueryWrapper<DrawGridShapeRelate> relateWrapper = new LambdaQueryWrapper<>();
|
||||
relateWrapper.eq(DrawGridShapeRelate::getGridId, gridId);
|
||||
List<DrawGridShapeRelate> relates = drawGridShapeRelateMapper.selectList(relateWrapper);
|
||||
for (DrawGridShapeRelate relate : relates) {
|
||||
// 根据图形ID查询客户
|
||||
LambdaQueryWrapper<DrawShapeCust> custWrapper = new LambdaQueryWrapper<>();
|
||||
custWrapper.eq(DrawShapeCust::getShapeId, relate.getShapeId());
|
||||
List<DrawShapeCust> shapeCusts = drawShapeCustMapper.selectList(custWrapper);
|
||||
for (DrawShapeCust shapeCust : shapeCusts) {
|
||||
CustGroupMember member = new CustGroupMember();
|
||||
member.setGroupId(custGroup.getId());
|
||||
member.setCustId(shapeCust.getCustId());
|
||||
member.setCustName(shapeCust.getCustName());
|
||||
member.setCustType(shapeCust.getCustType());
|
||||
member.setCreateTime(new Date());
|
||||
memberList.add(member);
|
||||
}
|
||||
}
|
||||
}
|
||||
return memberList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.ruoyi.group.mapper.CustGroupMapper">
|
||||
|
||||
<select id="selectCustGroupList" resultType="CustGroupVO">
|
||||
SELECT
|
||||
cg.id,
|
||||
cg.group_name,
|
||||
cg.group_mode,
|
||||
cg.create_mode,
|
||||
cg.user_name,
|
||||
cg.nick_name,
|
||||
cg.dept_id,
|
||||
cg.share_enabled,
|
||||
cg.share_dept_ids,
|
||||
cg.group_status,
|
||||
cg.create_by,
|
||||
cg.create_time,
|
||||
cg.update_by,
|
||||
cg.update_time,
|
||||
cg.remark,
|
||||
cg.del_flag,
|
||||
(SELECT COUNT(*) FROM ibs_cust_group_member cgm WHERE cgm.group_id = cg.id AND cgm.del_flag = '0') AS cust_count
|
||||
FROM ibs_cust_group cg
|
||||
<where>
|
||||
cg.del_flag = '0'
|
||||
and create_status = '1'
|
||||
<if test="dto.groupName != null and dto.groupName != ''">
|
||||
AND cg.group_name LIKE CONCAT('%', #{dto.groupName}, '%')
|
||||
</if>
|
||||
<if test="dto.groupMode != null and dto.groupMode != ''">
|
||||
AND cg.group_mode = #{dto.groupMode}
|
||||
</if>
|
||||
<if test="dto.createMode != null and dto.createMode != ''">
|
||||
AND cg.create_mode = #{dto.createMode}
|
||||
</if>
|
||||
<if test="dto.groupStatus != null and dto.groupStatus != ''">
|
||||
AND cg.group_status = #{dto.groupStatus}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY cg.create_time DESC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -97,6 +97,13 @@ public class GridCmpmController extends BaseController {
|
||||
return gridCmpmService.selectCustManagerResult();
|
||||
}
|
||||
|
||||
@GetMapping("/custManager/custLevel/list")
|
||||
@Log(title = "绩效网格-管户报表星级列表")
|
||||
@ApiOperation("管户报表星级列表")
|
||||
public AjaxResult getCustLevelListForManager() {
|
||||
return success(gridCmpmService.getCustLevelListForManager());
|
||||
}
|
||||
|
||||
@GetMapping("/custLevel/count")
|
||||
@Log(title = "绩效网格-查询客户分层等级")
|
||||
@ApiOperation("查询客户分层等级")
|
||||
|
||||
@@ -18,4 +18,8 @@ public class CustManagerDTO {
|
||||
@ApiModelProperty(value = "网点名",notes = "")
|
||||
private String branchId;
|
||||
|
||||
/** 状态类型:current(本月)、last(上月)、rise(上升)、fall(下降) */
|
||||
@ApiModelProperty(value = "状态类型", notes = "current(本月)、last(上月)、rise(上升)、fall(下降)")
|
||||
private String statusType;
|
||||
|
||||
}
|
||||
|
||||
@@ -152,6 +152,13 @@ public class GridCmpmService {
|
||||
return new DwbRetailResultVO();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取管户报表星级列表
|
||||
*/
|
||||
public List<String> getCustLevelListForManager() {
|
||||
return gridCmpmMapper.getCustLevelList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 每月定时设置统计数据
|
||||
*/
|
||||
@@ -226,7 +233,6 @@ public class GridCmpmService {
|
||||
// 获取上月各星级客户数
|
||||
Map<String, Integer> historyLevelCountMap = getCustLevelCountMap(custManagerDTO, "last");
|
||||
Map<String, Integer> custLevelCompLm = calculateLevelChanges(historyLevelCountMap, currentLevelCountMap);
|
||||
vo.setCustLevelCompLm(custLevelCompLm);
|
||||
|
||||
// 在 vo.setCustLevelCompLm 之前,按顺序重新组织数据
|
||||
String[] order = {"5星", "4星", "3星", "2星", "1星", "基础", "长尾"};
|
||||
|
||||
@@ -35,4 +35,12 @@ public interface RegionGridListService {
|
||||
|
||||
List<RegionGridListVO> getSecGridListByManager(RegionGridListDTO regionGridListDTO);
|
||||
|
||||
/**
|
||||
* 查询网格内所有客户(不限制客户类型,用于导入客群)
|
||||
* @param regionGrid 地理网格对象
|
||||
* @return 网格内所有客户列表
|
||||
*/
|
||||
List<RegionCustUser> selectAllCustFromGrid(RegionGrid regionGrid);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.ibs.grid.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.ruoyi.common.core.domain.entity.SysDept;
|
||||
import com.ruoyi.common.exception.ServiceException;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.bean.BeanUtils;
|
||||
import com.ruoyi.ibs.draw.domain.dto.grid.CustCountDTO;
|
||||
@@ -429,4 +430,25 @@ public class RegionGridListServiceImpl implements RegionGridListService {
|
||||
|
||||
return secGridListByManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询网格内所有客户(不限制客户类型,用于导入客群)
|
||||
* @param regionGrid 地理网格对象
|
||||
* @return 网格内所有客户列表
|
||||
*/
|
||||
@Override
|
||||
public List<RegionCustUser> selectAllCustFromGrid(RegionGrid regionGrid) {
|
||||
LambdaQueryWrapper<RegionCustUser> 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());
|
||||
}
|
||||
// 不限制客户类型,查询所有类型的客户
|
||||
return regionCustUserMapper.selectList(queryWrapper);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -142,6 +142,28 @@
|
||||
<if test="managerId != null and managerId !='' ">and manager_id = #{managerId}</if>
|
||||
<if test="outletId != null and outletId !='' ">and outlet_id = #{outletId}</if>
|
||||
<if test="branchId != null and branchId !='' ">and branch_id = #{branchId}</if>
|
||||
<if test="statusType != null and statusType !=''">
|
||||
<choose>
|
||||
<when test="statusType == 'current'">
|
||||
<!-- 本月:本月是指定星级的客户 -->
|
||||
and cust_level = #{custLevel}
|
||||
</when>
|
||||
<when test="statusType == 'last'">
|
||||
<!-- 上月:上月是指定星级的客户 -->
|
||||
and cust_level_lm = #{custLevel}
|
||||
</when>
|
||||
<when test="statusType == 'rise'">
|
||||
<!-- 上升:上升到指定星级的客户(本月是指定星级,上月星级更低) -->
|
||||
and cust_level = #{custLevel}
|
||||
and cust_level_comp_lm like '上升%'
|
||||
</when>
|
||||
<when test="statusType == 'fall'">
|
||||
<!-- 下降:从指定星级下降的客户(上月是指定星级,本月星级更低) -->
|
||||
and cust_level_lm = #{custLevel}
|
||||
and cust_level_comp_lm like '下降%'
|
||||
</when>
|
||||
</choose>
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
@@ -438,6 +460,7 @@
|
||||
select distinct
|
||||
cust_level
|
||||
from dwb_retail_cust_level_manager_detail_875
|
||||
order by cust_level
|
||||
</select>
|
||||
|
||||
<select id="getCustCountByLevel" resultType="int">
|
||||
|
||||
1
pom.xml
1
pom.xml
@@ -226,6 +226,7 @@
|
||||
<module>ruoyi-generator</module>
|
||||
<module>ruoyi-common</module>
|
||||
<module>ibs</module>
|
||||
<module>ibs-group</module>
|
||||
</modules>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
|
||||
@@ -74,6 +74,13 @@
|
||||
<version>${ruoyi.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 客群管理-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ibs-group</artifactId>
|
||||
<version>${ruoyi.version}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -79,7 +79,7 @@ spring:
|
||||
# 地址
|
||||
host: 116.62.17.81
|
||||
# 端口,默认为6379
|
||||
port: 6380
|
||||
port: 6379
|
||||
# 数据库索引
|
||||
database: 0
|
||||
# 密码
|
||||
|
||||
@@ -100,50 +100,6 @@ public class SysLoginService
|
||||
return tokenService.createToken(loginUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试登录
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @return 结果
|
||||
*/
|
||||
public String login(String username, String password)
|
||||
{
|
||||
// 登录前置校验
|
||||
loginPreCheck(username, password);
|
||||
// 用户验证
|
||||
Authentication authentication = null;
|
||||
try
|
||||
{
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
|
||||
AuthenticationContextHolder.setContext(authenticationToken);
|
||||
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
|
||||
authentication = authenticationManager.authenticate(authenticationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (e instanceof BadCredentialsException)
|
||||
{
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
|
||||
throw new UserPasswordNotMatchException();
|
||||
}
|
||||
else
|
||||
{
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
AuthenticationContextHolder.clearContext();
|
||||
}
|
||||
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
|
||||
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
|
||||
recordLoginInfo(loginUser.getUserId());
|
||||
// 生成token
|
||||
return tokenService.createToken(loginUser);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验验证码
|
||||
*
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = DEV支行数智管理平台系统
|
||||
VUE_APP_TITLE = UAT支行数智管理平台系统
|
||||
|
||||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = UAT支行数智管理平台系统
|
||||
VUE_APP_TITLE = PRE支行数智管理平台系统
|
||||
|
||||
NODE_ENV = staging
|
||||
|
||||
|
||||
4
ruoyi-ui/.gitignore
vendored
4
ruoyi-ui/.gitignore
vendored
@@ -21,6 +21,4 @@ selenium-debug.log
|
||||
|
||||
dist.zip
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
vue.config.js
|
||||
yarn.lock
|
||||
@@ -18,7 +18,7 @@
|
||||
</style>
|
||||
<script>
|
||||
// 这里一定要配置ak 否则无法使用
|
||||
window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
|
||||
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM"
|
||||
</script>
|
||||
<script>
|
||||
const result = new URLSearchParams(window.location.search)
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</style>
|
||||
<script>
|
||||
// 这里一定要配置ak 否则无法使用
|
||||
window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
|
||||
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM"
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
</style>
|
||||
<script>
|
||||
// 这里一定要配置ak 否则无法使用
|
||||
window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
|
||||
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM"
|
||||
</script>
|
||||
<script>
|
||||
const result = new URLSearchParams(window.location.search)
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</style>
|
||||
<script>
|
||||
// 这里一定要配置ak 否则无法使用
|
||||
window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
|
||||
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM"
|
||||
</script>
|
||||
<script type="text/javascript" src="./script/getscript.js?type=webgl&v=1.0&services=&t=20230529114224"></script>
|
||||
<!-- 引入logisticsgl的sdk -->
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<script>
|
||||
const sdk = new QuHuaSdk({
|
||||
ak: 'L7KaAZUYPVSD40nYT09rWWgIdZKUesiX',
|
||||
webAk: 'mokVj0S4sGE9av6NBwy8WHY0xnQsucbE',
|
||||
webAk: 't6k6UC2IZR40Un8kkqM4RXlaQb4FulyM',
|
||||
domId: 'box',
|
||||
defaultCenterCity: "杭州市", // 非必填
|
||||
_baseUrl: window.NODE_ENV === "production" ? "http://64.202.32.20:5001/logisticsWeb-quhua-intranet" : "http://158.234.96.76:5001/logisticsWeb-quhua-intranet", // 固定格式,必填
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</style>
|
||||
<script>
|
||||
// 这里一定要配置ak 否则无法使用
|
||||
window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
|
||||
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM"
|
||||
</script>
|
||||
<script>
|
||||
const result = new URLSearchParams(window.location.search)
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</style>
|
||||
<script>
|
||||
// 这里一定要配置ak 否则无法使用
|
||||
window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
|
||||
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM"
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</style>
|
||||
<script>
|
||||
// 这里一定要配置ak 否则无法使用
|
||||
window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
|
||||
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM"
|
||||
</script>
|
||||
<script>
|
||||
const result = new URLSearchParams(window.location.search)
|
||||
|
||||
@@ -8,10 +8,9 @@
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<script>
|
||||
// 这里一定要配置ak 否则无法使用
|
||||
window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
|
||||
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM"
|
||||
</script>
|
||||
<script type="text/javascript" src="<%= VUE_APP_BAIDU_PATH %>"></script>
|
||||
<!-- <script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"></script> -->
|
||||
<link rel="stylesheet" type="text/css" href="<%= VUE_APP_BAIDU_CSS_PATH %>" />
|
||||
<script type="text/javascript" src="\baidu\script\index.umd.min.js"></script>
|
||||
|
||||
|
||||
@@ -14,4 +14,12 @@ export function gridCmpmCustManagerResult(data) {
|
||||
method: 'get',
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
// 获取管户报表星级列表
|
||||
export function getCustLevelList() {
|
||||
return request({
|
||||
url: '/grid/cmpm/custManager/custLevel/list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
@@ -237,14 +237,6 @@
|
||||
@click.stop="updateAreaInfo"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<!-- 查看客户 -->
|
||||
<el-tooltip placement="top" effect="light" content="查看客户">
|
||||
<i
|
||||
class="el-icon-user icon-area"
|
||||
@click.stop="previewCustomer"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<!-- 删除区域 -->
|
||||
<el-tooltip placement="top" effect="light" content="删除区域">
|
||||
<el-popconfirm
|
||||
title="确定删除吗?"
|
||||
@@ -279,13 +271,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<!-- 查看客户模态框 -->
|
||||
<customer-modal
|
||||
ref="customerModal"
|
||||
cardType="featured"
|
||||
:detailInfo="areaForm"
|
||||
:btnType="'2'"
|
||||
/>
|
||||
<div class="search-box">
|
||||
<el-form size="small" class="myForm">
|
||||
<el-form-item style="width: 130px">
|
||||
@@ -352,7 +337,6 @@ import { businessBelongList } from '@/views/grid/create/utils'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { Message } from 'element-ui'
|
||||
import { isEmpty } from 'lodash'
|
||||
import CustomerModal from '@/views/grid/map/draw-area/customer-modal.vue'
|
||||
const polygonOptions = {
|
||||
strokeColor: '#5E87DB',
|
||||
strokeWeight: 3,
|
||||
@@ -371,9 +355,6 @@ const labelOptions = {
|
||||
}
|
||||
export default {
|
||||
name: 'BMapPolygonEditor',
|
||||
components: {
|
||||
CustomerModal
|
||||
},
|
||||
props: ['layerInfo'],
|
||||
data() {
|
||||
return {
|
||||
@@ -1226,12 +1207,6 @@ export default {
|
||||
this.$store.dispatch('setIsDrawing', true)
|
||||
this.currentPolygon.enableEditing()
|
||||
},
|
||||
/**
|
||||
* 查看客户
|
||||
*/
|
||||
previewCustomer() {
|
||||
this.$refs.customerModal.onOpen()
|
||||
},
|
||||
setForm(data) {
|
||||
this.areaForm = data
|
||||
this.visible = true
|
||||
|
||||
@@ -35,7 +35,7 @@ export default {
|
||||
initSdk(){
|
||||
this.sdk = new QuHuaSdk({
|
||||
ak: 'L7KaAZUYPVSD40nYT09rWWgIdZKUesiX',
|
||||
webAk: 'mokVj0S4sGE9av6NBwy8WHY0xnQsucbE',
|
||||
webAk: 't6k6UC2IZR40Un8kkqM4RXlaQb4FulyM',
|
||||
domId: 'box',
|
||||
defaultCenterCity: "杭州市", // 非必填
|
||||
_baseUrl: "http://158.234.96.76:5001/logisticsWeb-quhua-intranet", // 固定格式,必填
|
||||
@@ -137,7 +137,7 @@ export default {
|
||||
onChange(){
|
||||
this.sdk = new QuHuaSdk({
|
||||
ak: 'L7KaAZUYPVSD40nYT09rWWgIdZKUesiX',
|
||||
webAk: 'mokVj0S4sGE9av6NBwy8WHY0xnQsucbE',
|
||||
webAk: 't6k6UC2IZR40Un8kkqM4RXlaQb4FulyM',
|
||||
domId: 'box',
|
||||
defaultCenterCity: "杭州市", // 非必填
|
||||
_baseUrl: "http://158.234.96.76:5001/logisticsWeb-quhua-intranet", // 固定格式,必填
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div>
|
||||
<!-- 统计卡片 -->
|
||||
<div :gutter="24" class="sum-box">
|
||||
<el-card class="box-card">
|
||||
<div class="my-span-checklist-title">
|
||||
总资产余额
|
||||
</div>
|
||||
<div class="my-span-checklist-main">
|
||||
<span>{{ cardInfo.custAumBal }}</span>
|
||||
<span>{{ formatYi(cardInfo.custAumBal) }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card class="box-card">
|
||||
@@ -14,7 +15,7 @@
|
||||
总资产余额较上月变动
|
||||
</div>
|
||||
<div class="my-span-checklist-main">
|
||||
<span>{{ cardInfo.aumBalCompLm }}</span>
|
||||
<span>{{ formatYi(cardInfo.aumBalCompLm) }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card class="box-card">
|
||||
@@ -22,7 +23,7 @@
|
||||
总资产余额月日均
|
||||
</div>
|
||||
<div class="my-span-checklist-main">
|
||||
<span>{{ cardInfo.custAumMonthAvg }}</span>
|
||||
<span>{{ formatYi(cardInfo.custAumMonthAvg) }}</span>
|
||||
</div>
|
||||
</el-card>
|
||||
<el-card class="box-card">
|
||||
@@ -66,6 +67,33 @@
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 搜索区域 -->
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" label-width="90px">
|
||||
<el-form-item label="客户星级" prop="custLevel">
|
||||
<el-select v-model="queryParams.custLevel" placeholder="请选择" clearable style="width: 150px">
|
||||
<el-option
|
||||
v-for="level in custLevelOptions"
|
||||
:key="level"
|
||||
:label="level"
|
||||
:value="level"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="星级状态" prop="statusType">
|
||||
<el-select v-model="queryParams.statusType" placeholder="请选择" clearable style="width: 150px">
|
||||
<el-option label="本月" value="current" />
|
||||
<el-option label="上月" value="last" />
|
||||
<el-option label="上升" value="rise" />
|
||||
<el-option label="下降" value="fall" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-card class="header-statics" style="display: none">
|
||||
<ul class="statics-cnt">
|
||||
<li>
|
||||
@@ -122,7 +150,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { gridCmpmCustManagerList, gridCmpmCustManagerResult } from '@/api/gridSearch/accountManageReport/index'
|
||||
import { gridCmpmCustManagerList, gridCmpmCustManagerResult, getCustLevelList } from '@/api/gridSearch/accountManageReport/index'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -137,19 +165,50 @@ import { gridCmpmCustManagerList, gridCmpmCustManagerResult } from '@/api/gridSe
|
||||
},
|
||||
tableData: [],
|
||||
loading: false,
|
||||
custLevelOptions: [],
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10
|
||||
pageSize: 10,
|
||||
custLevel: null,
|
||||
statusType: null
|
||||
},
|
||||
total: 0,
|
||||
topData: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 从 URL 参数获取筛选条件
|
||||
const { custLevel, statusType } = this.$route.query
|
||||
if (custLevel) {
|
||||
this.queryParams.custLevel = custLevel
|
||||
}
|
||||
if (statusType) {
|
||||
this.queryParams.statusType = statusType
|
||||
}
|
||||
this.loadCustLevelOptions()
|
||||
this.getData()
|
||||
this.getSum()
|
||||
},
|
||||
methods: {
|
||||
/** 加载客户星级选项 */
|
||||
loadCustLevelOptions() {
|
||||
getCustLevelList().then(res => {
|
||||
this.custLevelOptions = res.data || []
|
||||
}).catch(() => {
|
||||
this.custLevelOptions = []
|
||||
})
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
handleQuery() {
|
||||
this.queryParams.pageNum = 1
|
||||
this.getData()
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.queryParams.custLevel = null
|
||||
this.queryParams.statusType = null
|
||||
this.handleQuery()
|
||||
},
|
||||
getData() {
|
||||
this.loading = true;
|
||||
gridCmpmCustManagerList(this.queryParams).then(res => {
|
||||
@@ -183,10 +242,21 @@ import { gridCmpmCustManagerList, gridCmpmCustManagerResult } from '@/api/gridSe
|
||||
},
|
||||
toSum(obj) {
|
||||
let sum = 0
|
||||
Object.keys(obj).map(key => {
|
||||
sum = sum + obj[key] * 1
|
||||
Object.keys(obj).forEach(key => {
|
||||
// 只计算2星级及以上的:key中包含"2星"、"3星"、"4星"、"5星"
|
||||
if (key.includes('2星') || key.includes('3星') || key.includes('4星') || key.includes('5星')) {
|
||||
sum += obj[key]
|
||||
}
|
||||
})
|
||||
return sum
|
||||
},
|
||||
/** 格式化为亿元 */
|
||||
formatYi(value) {
|
||||
if (value === null || value === '' || value === undefined) return '-'
|
||||
const num = parseFloat(value)
|
||||
if (isNaN(num)) return '-'
|
||||
// 换算为亿元,保留2位小数
|
||||
return (num / 100000000).toFixed(2) + '亿元'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@
|
||||
</div>
|
||||
<div class="top_num">
|
||||
<span style="font-size: 14px;">较上月变动</span>
|
||||
<span v-if="String(item.inc).includes('-')" style=" font-size: 14px;color: #EF3F35">{{ changeData(item.inc)
|
||||
<span v-if="String(item.inc).includes('-')" style=" font-size: 14px;color: #00B453">{{ changeData(item.inc)
|
||||
}}<i class="el-icon-caret-bottom"></i></span>
|
||||
<span v-else style=" font-size: 14px;color: #00B453">{{ changeData(item.inc) }}<i
|
||||
<span v-else style=" font-size: 14px;color: #EF3F35">{{ changeData(item.inc) }}<i
|
||||
class="el-icon-caret-top"></i></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,18 +30,24 @@
|
||||
<div class="vt-main top-vt-main">
|
||||
<div class="top_num">
|
||||
<span>上月值</span>
|
||||
<span style="font-size: 14px;">{{ item.yiAmt }}</span>
|
||||
<span style="font-size: 14px; cursor: pointer; color: #409EFF;" @click="goToCustManager(item.itemNm, 'last')">{{ item.yiAmt }}</span>
|
||||
</div>
|
||||
<div class="top_num">
|
||||
<span>当月值</span>
|
||||
<span style="font-size: 14px;">{{ item.inc }}</span>
|
||||
<span style="font-size: 14px; cursor: pointer; color: #409EFF;" @click="goToCustManager(item.itemNm, 'current')">{{ item.inc }}</span>
|
||||
</div>
|
||||
<div class="top_num">
|
||||
<span style="font-size: 14px;">较上月变动</span>
|
||||
<span v-if="String(item.curAmt).includes('-')" style=" font-size: 14px;color: #EF3F35">{{ item.curAmt
|
||||
}}<i class="el-icon-caret-bottom"></i></span>
|
||||
<span v-else style=" font-size: 14px;color: #00B453">{{ item.curAmt }}<i
|
||||
class="el-icon-caret-top"></i></span>
|
||||
<span v-if="String(item.curAmt).includes('-')"
|
||||
style="font-size: 14px; color: #00B453; cursor: pointer;"
|
||||
@click="goToCustManager(item.itemNm, 'fall')">
|
||||
{{ item.curAmt }}<i class="el-icon-caret-bottom"></i>
|
||||
</span>
|
||||
<span v-else
|
||||
style="font-size: 14px; color: #EF3F35; cursor: pointer;"
|
||||
@click="goToCustManager(item.itemNm, 'rise')">
|
||||
{{ item.curAmt }}<i class="el-icon-caret-top"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -875,6 +881,23 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
/** 跳转到管户报表页面 */
|
||||
goToCustManager(itemNm, statusType) {
|
||||
// 从 itemNm 中提取星级,如 "3星客户" → "3星","基础客户" → "基础"
|
||||
// 使用正则匹配以"星"或"基础"或"长尾"开头的内容
|
||||
let custLevel = itemNm.replace('客户', '').trim()
|
||||
// 如果没有"客户"后缀,直接使用整个名称
|
||||
if (custLevel === itemNm) {
|
||||
custLevel = itemNm
|
||||
}
|
||||
this.$router.push({
|
||||
path: '/gridSearch/accountManageReport',
|
||||
query: {
|
||||
custLevel: custLevel,
|
||||
statusType: statusType
|
||||
}
|
||||
})
|
||||
},
|
||||
getData() {
|
||||
this.loading = true
|
||||
if (this.selectedTab === '3') {
|
||||
|
||||
Reference in New Issue
Block a user