Compare commits

...

15 Commits

Author SHA1 Message Date
23ff1b0b1d 0320-北仑:企业九维数据+客户分层导入+消息提醒;版本更新通知公告限制部门可见 2026-03-20 15:52:21 +08:00
44fd20316d 0318-海宁菜单调整 2026-03-19 09:08:13 +08:00
e3e26574c6 Merge branch 'master-yly'
# Conflicts:
#	CLAUDE.md
2026-03-18 16:43:40 +08:00
wkc
76c68cd544 Merge branch 'dev' 2026-03-06 13:40:54 +08:00
wkc
d897e12335 地图key 2026-03-06 13:38:05 +08:00
wkc
3eafd1b9e2 feat(featured-areas): 实现 previewCustomer 方法 2026-02-28 16:34:29 +08:00
wkc
67e14363e3 feat(featured-areas): 添加查看客户图标按钮 2026-02-28 16:26:34 +08:00
wkc
b15cc4fdcb feat(featured-areas): 添加 CustomerModal 组件引用 2026-02-28 16:19:05 +08:00
wkc
4ec1544660 feat(featured-areas): 引入 CustomerModal 组件 2026-02-28 16:10:39 +08:00
wkc
c432b04075 docs: 添加特色区域查看客户功能实施计划
- 详细的6个任务分解
- 每个步骤包含具体代码和验证方法
- 包含完整的测试流程
- 包含回滚方案
2026-02-28 16:03:35 +08:00
wkc
8d2e5386ac docs: 添加特色区域查看客户功能设计文档
- 在特色区域详情窗口添加查看客户按钮的设计方案
- 包含需求分析、技术设计、实施计划、测试计划等内容
2026-02-28 16:01:19 +08:00
wkc
7efc23dfd2 更正页面标题 2026-02-28 15:45:44 +08:00
wkc
3c27b192a4 claude init 2026-02-28 15:43:04 +08:00
wkc
aeda8f2fda Merge branch 'master' into dev 2026-02-28 15:19:18 +08:00
wkc
1fb945a40f redis配置更新 2026-02-26 16:37:37 +08:00
36 changed files with 2234 additions and 37 deletions

View File

@@ -0,0 +1,308 @@
# 特色区域详情窗口添加查看客户功能设计文档
## 文档信息
- **日期**: 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 | 初始版本 |

View File

@@ -0,0 +1,389 @@
# 特色区域查看客户功能实施计划
> **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/

View File

@@ -18,8 +18,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.rmi.ServerException; import java.rmi.ServerException;
@@ -220,4 +222,18 @@ public class MyCustomerController extends BaseController {
} }
return AjaxResult.success(iSysCampaignGroupCustomerService.appointCustCamp( custId, custName, custIdc, custPhone, custIsn,socialCreditCode,lpName, campaignId, custType)); return AjaxResult.success(iSysCampaignGroupCustomerService.appointCustCamp( custId, custName, custIdc, custPhone, custIsn,socialCreditCode,lpName, campaignId, custType));
} }
@Log(title = "我的客户-异步导入企业客户价值分层", businessType = com.ruoyi.common.enums.BusinessType.IMPORT)
@PostMapping(value = "/importBusinessCustLevelAsync", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@ApiOperation("异步导入企业客户价值分层")
public AjaxResult importBusinessCustLevelAsync(@RequestPart("file") MultipartFile file) {
return AjaxResult.success("导入任务已提交,后台正在处理", myCustomerService.importBusinessCustLevelAsync(file));
}
@Log(title = "我的客户-查询企业客户价值分层导入状态")
@GetMapping("/importBusinessCustLevelStatus/{taskId}")
@ApiOperation("查询企业客户价值分层导入状态")
public AjaxResult importBusinessCustLevelStatus(@PathVariable String taskId) {
return AjaxResult.success(myCustomerService.getBusinessCustLevelImportStatus(taskId));
}
} }

View File

@@ -0,0 +1,14 @@
package com.ruoyi.ibs.customerselect.domain;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
@Data
public class BusinessCustLevelImportExcelDTO {
@ExcelProperty("统信码")
private String socialCreditCode;
@ExcelProperty("价值分层")
private String custLevel;
}

View File

@@ -0,0 +1,30 @@
package com.ruoyi.ibs.customerselect.domain;
import lombok.Data;
import java.io.Serializable;
@Data
public class BusinessCustLevelImportTaskVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 0-处理中 1-成功 2-失败
*/
private String status;
private String message;
private Integer totalCount;
private Integer successCount;
private Integer ignoredCount;
private String userName;
private String createTime;
private String finishTime;
}

View File

@@ -2,6 +2,7 @@ package com.ruoyi.ibs.customerselect.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.common.core.domain.entity.CustInfoBusiness; import com.ruoyi.common.core.domain.entity.CustInfoBusiness;
import com.ruoyi.ibs.customerselect.domain.BusinessCustLevelImportExcelDTO;
import com.ruoyi.ibs.customerselect.domain.CustBaseInfo; import com.ruoyi.ibs.customerselect.domain.CustBaseInfo;
import com.ruoyi.ibs.customerselect.domain.CustInfoDeleteFromAnchor; import com.ruoyi.ibs.customerselect.domain.CustInfoDeleteFromAnchor;
import com.ruoyi.ibs.customerselect.domain.CustInfoUpdateFromAnchor; import com.ruoyi.ibs.customerselect.domain.CustInfoUpdateFromAnchor;
@@ -242,4 +243,10 @@ public interface CustInfoBusinessMapper extends BaseMapper<CustInfoBusiness>
public int insertCustomersToBusinessByScCode(List<SysGroupCustomer> sysGroupCustomers); public int insertCustomersToBusinessByScCode(List<SysGroupCustomer> sysGroupCustomers);
List<CustInfoBusiness> selectRecord(String socialCreditCode); List<CustInfoBusiness> selectRecord(String socialCreditCode);
List<String> selectExistingSocialCreditCodes(@Param("socialCreditCodes") List<String> socialCreditCodes,
@Param("deptCode") String deptCode);
int batchUpdateCustLevelBySocialCreditCode(@Param("list") List<BusinessCustLevelImportExcelDTO> list,
@Param("deptCode") String deptCode);
} }

View File

@@ -1,6 +1,7 @@
package com.ruoyi.ibs.customerselect.service; package com.ruoyi.ibs.customerselect.service;
import com.ruoyi.ibs.customerselect.domain.*; import com.ruoyi.ibs.customerselect.domain.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
@@ -41,4 +42,8 @@ public interface IMyCustomerService {
public CustListSearchVo selectCustomListSearchVo(CustBaseInfo sysCustomerBasedata); public CustListSearchVo selectCustomListSearchVo(CustBaseInfo sysCustomerBasedata);
MerchantMcspInfo selectmerchantMessage(String custId); MerchantMcspInfo selectmerchantMessage(String custId);
String importBusinessCustLevelAsync(MultipartFile file);
BusinessCustLevelImportTaskVO getBusinessCustLevelImportStatus(String taskId);
} }

View File

@@ -1,13 +1,19 @@
package com.ruoyi.ibs.customerselect.service.Impl; package com.ruoyi.ibs.customerselect.service.Impl;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.annotation.DataScope; import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.CustInfoBusiness; import com.ruoyi.common.core.domain.entity.CustInfoBusiness;
import com.ruoyi.common.core.domain.entity.SysDictData; import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.ibs.dashboard.service.NotificationService;
import com.ruoyi.ibs.customerselect.domain.BusinessCustLevelImportExcelDTO;
import com.ruoyi.ibs.customerselect.domain.BusinessCustLevelImportTaskVO;
import com.ruoyi.ibs.customerselect.domain.*; import com.ruoyi.ibs.customerselect.domain.*;
import com.ruoyi.ibs.customerselect.domain.vo.GridRelateVO; import com.ruoyi.ibs.customerselect.domain.vo.GridRelateVO;
import com.ruoyi.ibs.customerselect.mapper.CustInfoBusinessMapper; import com.ruoyi.ibs.customerselect.mapper.CustInfoBusinessMapper;
@@ -32,9 +38,16 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -45,6 +58,10 @@ import java.util.stream.Collectors;
@Service @Service
public class MyCustomerServiceImpl implements IMyCustomerService { public class MyCustomerServiceImpl implements IMyCustomerService {
private static final String BUSINESS_CUST_LEVEL_IMPORT_TASK_KEY = "BUSINESS_CUST_LEVEL_IMPORT_TASK_";
private static final int BUSINESS_CUST_LEVEL_IMPORT_BATCH_SIZE = 1000;
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Autowired @Autowired
private CustInfoBusinessMapper custInfoBusinessMapper; private CustInfoBusinessMapper custInfoBusinessMapper;
@@ -80,6 +97,12 @@ public class MyCustomerServiceImpl implements IMyCustomerService {
@Resource @Resource
private RedisCache redisCache; private RedisCache redisCache;
@Resource
private NotificationService notificationService;
@Resource(name = "excelImportExecutor")
private ExecutorService excelImportExecutor;
private static Logger logger = LoggerFactory.getLogger(MyCustomerServiceImpl.class); private static Logger logger = LoggerFactory.getLogger(MyCustomerServiceImpl.class);
/** /**
@@ -467,5 +490,154 @@ public class MyCustomerServiceImpl implements IMyCustomerService {
return merchantMcspInfoMapper.selectOne(new LambdaQueryWrapper<MerchantMcspInfo>().eq(MerchantMcspInfo::getCustId,custId)); return merchantMcspInfoMapper.selectOne(new LambdaQueryWrapper<MerchantMcspInfo>().eq(MerchantMcspInfo::getCustId,custId));
} }
@Override
public String importBusinessCustLevelAsync(MultipartFile file) {
if (file == null || file.isEmpty()) {
throw new ServiceException("导入文件不能为空");
}
byte[] fileBytes;
try {
fileBytes = file.getBytes();
} catch (IOException e) {
throw new ServiceException("读取导入文件失败");
}
String taskId = IdUtils.fastSimpleUUID();
String userName = SecurityUtils.getUsername();
String deptCode = SecurityUtils.getHeadId();
BusinessCustLevelImportTaskVO taskVO = new BusinessCustLevelImportTaskVO();
taskVO.setStatus("0");
taskVO.setMessage("导入任务已提交,后台正在处理");
taskVO.setTotalCount(0);
taskVO.setSuccessCount(0);
taskVO.setIgnoredCount(0);
taskVO.setUserName(userName);
taskVO.setCreateTime(formatNow());
cacheBusinessCustLevelImportTask(taskId, taskVO);
excelImportExecutor.submit(() -> doImportBusinessCustLevel(taskId, userName, deptCode, fileBytes));
return taskId;
}
@Override
public BusinessCustLevelImportTaskVO getBusinessCustLevelImportStatus(String taskId) {
BusinessCustLevelImportTaskVO taskVO = redisCache.getCacheObject(getBusinessCustLevelImportTaskKey(taskId));
if (taskVO == null) {
throw new ServiceException("导入任务不存在或已过期");
}
return taskVO;
}
private void doImportBusinessCustLevel(String taskId, String userName, String deptCode, byte[] fileBytes) {
BusinessCustLevelImportTaskVO taskVO = redisCache.getCacheObject(getBusinessCustLevelImportTaskKey(taskId));
if (taskVO == null) {
taskVO = new BusinessCustLevelImportTaskVO();
taskVO.setUserName(userName);
taskVO.setCreateTime(formatNow());
}
try {
List<BusinessCustLevelImportExcelDTO> importRows = EasyExcel.read(new ByteArrayInputStream(fileBytes))
.head(BusinessCustLevelImportExcelDTO.class)
.sheet()
.doReadSync();
Map<String, String> levelMap = new LinkedHashMap<>();
if (importRows != null) {
for (BusinessCustLevelImportExcelDTO row : importRows) {
String socialCreditCode = normalizeImportCell(row.getSocialCreditCode());
String custLevel = normalizeImportCell(row.getCustLevel());
if (StringUtils.isEmpty(socialCreditCode)) {
continue;
}
levelMap.put(socialCreditCode, custLevel);
}
}
if (levelMap.isEmpty()) {
throw new ServiceException("Excel中未识别到有效的统信码和价值分层数据");
}
List<String> existingSocialCreditCodes = getExistingBusinessSocialCreditCodes(new ArrayList<>(levelMap.keySet()), deptCode);
Set<String> existingCodeSet = new HashSet<>(existingSocialCreditCodes);
List<BusinessCustLevelImportExcelDTO> updateList = new ArrayList<>();
for (Map.Entry<String, String> entry : levelMap.entrySet()) {
if (!existingCodeSet.contains(entry.getKey())) {
continue;
}
BusinessCustLevelImportExcelDTO dto = new BusinessCustLevelImportExcelDTO();
dto.setSocialCreditCode(entry.getKey());
dto.setCustLevel(entry.getValue());
updateList.add(dto);
}
batchUpdateBusinessCustLevel(updateList, deptCode);
int totalCount = levelMap.size();
int successCount = updateList.size();
int ignoredCount = totalCount - successCount;
String message = String.format("公司客户视图分层分类数据导入完成,成功更新%d条忽略%d条", successCount, ignoredCount);
taskVO.setStatus("1");
taskVO.setMessage(message);
taskVO.setTotalCount(totalCount);
taskVO.setSuccessCount(successCount);
taskVO.setIgnoredCount(ignoredCount);
taskVO.setFinishTime(formatNow());
cacheBusinessCustLevelImportTask(taskId, taskVO);
notificationService.sendNotification(userName, message);
} catch (Exception e) {
String errorMsg = StringUtils.isNotEmpty(e.getMessage()) ? e.getMessage() : "导入失败,请检查文件内容";
taskVO.setStatus("2");
taskVO.setMessage(errorMsg);
taskVO.setFinishTime(formatNow());
cacheBusinessCustLevelImportTask(taskId, taskVO);
notificationService.sendNotification(userName, "公司客户视图分层分类数据导入失败:" + errorMsg);
logger.error("公司客户视图分层分类数据导入失败taskId={}", taskId, e);
}
}
private void batchUpdateBusinessCustLevel(List<BusinessCustLevelImportExcelDTO> updateList, String deptCode) {
if (updateList == null || updateList.isEmpty()) {
return;
}
for (int i = 0; i < updateList.size(); i += BUSINESS_CUST_LEVEL_IMPORT_BATCH_SIZE) {
int endIndex = Math.min(i + BUSINESS_CUST_LEVEL_IMPORT_BATCH_SIZE, updateList.size());
custInfoBusinessMapper.batchUpdateCustLevelBySocialCreditCode(updateList.subList(i, endIndex), deptCode);
}
}
private List<String> getExistingBusinessSocialCreditCodes(List<String> socialCreditCodes, String deptCode) {
if (socialCreditCodes == null || socialCreditCodes.isEmpty()) {
return Collections.emptyList();
}
List<String> existingSocialCreditCodes = new ArrayList<>();
for (int i = 0; i < socialCreditCodes.size(); i += BUSINESS_CUST_LEVEL_IMPORT_BATCH_SIZE) {
int endIndex = Math.min(i + BUSINESS_CUST_LEVEL_IMPORT_BATCH_SIZE, socialCreditCodes.size());
List<String> batchCodes = socialCreditCodes.subList(i, endIndex);
List<String> batchResult = custInfoBusinessMapper.selectExistingSocialCreditCodes(batchCodes, deptCode);
if (batchResult != null && !batchResult.isEmpty()) {
existingSocialCreditCodes.addAll(batchResult);
}
}
return existingSocialCreditCodes;
}
private void cacheBusinessCustLevelImportTask(String taskId, BusinessCustLevelImportTaskVO taskVO) {
redisCache.setCacheObject(getBusinessCustLevelImportTaskKey(taskId), taskVO, 24, TimeUnit.HOURS);
}
private String getBusinessCustLevelImportTaskKey(String taskId) {
return BUSINESS_CUST_LEVEL_IMPORT_TASK_KEY + taskId;
}
private String normalizeImportCell(String value) {
if (value == null) {
return null;
}
String trimmedValue = value.trim();
return trimmedValue.isEmpty() ? null : trimmedValue;
}
private String formatNow() {
return LocalDateTime.now().format(DATE_TIME_FORMATTER);
}
} }

View File

@@ -173,6 +173,7 @@ public class DashboardController extends BaseController {
public TableDataInfo list(SysNotice notice) public TableDataInfo list(SysNotice notice)
{ {
startPage(); startPage();
notice.setCurrentHeadDeptId(SecurityUtils.getHeadId() + "000");
List<SysNotice> list = noticeService.selectNoticeList(notice); List<SysNotice> list = noticeService.selectNoticeList(notice);
return getDataTable(list); return getDataTable(list);
} }

View File

@@ -23,44 +23,58 @@ public class Ent9vPortraitOrc implements Serializable {
private String uniscid; private String uniscid;
/** 客户内码 */ /** 客户内码 */
@TableField("cst_id")
private String cstId; private String cstId;
/** 机构号 */ /** 机构号 */
@TableField("org_no")
private String orgNo; private String orgNo;
/** score_1合规经营 */ /** score_1合规经营 */
@TableField("score_1")
private BigDecimal score1; private BigDecimal score1;
/** score_2风险准入 */ /** score_2风险准入 */
@TableField("score_2")
private BigDecimal score2; private BigDecimal score2;
/** score_3高管信用评价 */ /** score_3高管信用评价 */
@TableField("score_3")
private String score3; private String score3;
/** score_4股东信用评价 */ /** score_4股东信用评价 */
@TableField("score_4")
private BigDecimal score4; private BigDecimal score4;
/** score_5社会贡献度 */ /** score_5社会贡献度 */
@TableField("score_5")
private BigDecimal score5; private BigDecimal score5;
/** score_6稳定经营 */ /** score_6稳定经营 */
@TableField("score_6")
private BigDecimal score6; private BigDecimal score6;
/** score_7经营能力 */ /** score_7经营能力 */
@TableField("score_7")
private BigDecimal score7; private BigDecimal score7;
/** score_8偿债能力 */ /** score_8偿债能力 */
@TableField("score_8")
private BigDecimal score8; private BigDecimal score8;
/** score_9潜在代偿资源 */ /** score_9潜在代偿资源 */
@TableField("score_9")
private BigDecimal score9; private BigDecimal score9;
/** 九维总分 */ /** 九维总分 */
@TableField("score_all")
private BigDecimal scoreAll; private BigDecimal scoreAll;
/** 九维总分排名 */ /** 九维总分排名 */
@TableField("score_all_rank")
private BigDecimal scoreAllRank; private BigDecimal scoreAllRank;
/** 会计日期 */ /** 会计日期 */
@TableField("dat_dt")
private String datDt; private String datDt;
} }

View File

@@ -1,5 +1,6 @@
package com.ruoyi.ibs.list.domain; package com.ruoyi.ibs.list.domain;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data; import lombok.Data;
@@ -21,206 +22,294 @@ public class NineVFinalInfoOrc implements Serializable {
private String uniscid; private String uniscid;
/** 机构号 */ /** 机构号 */
@TableField("org_no")
private String orgNo; private String orgNo;
/** 一、企业合规经营模块 */
/** 是否存在经营异常名录信息 */ /** 是否存在经营异常名录信息 */
@TableField("score_1_1")
private String score11; private String score11;
/** 是否存在严重违法失信企业名单信息 */ /** 是否存在严重违法失信企业名单信息 */
@TableField("score_1_2")
private String score12; private String score12;
/** 企业环境行为信仰登记是否为"E"或D */ /** 企业环境行为信仰登记是否为"E"或D */
@TableField("score_1_3")
private String score13; private String score13;
/** 是否存在税务重大税收违法黑名单信息 */ /** 是否存在税务重大税收违法黑名单信息 */
@TableField("score_1_4")
private String score14; private String score14;
/** 是否存在拖欠工资黑名单 */ /** 是否存在拖欠工资黑名单 */
@TableField("score_1_5")
private String score15; private String score15;
/** 是否存在工商吊销企业信息 */ /** 是否存在工商吊销企业信息 */
@TableField("score_1_6")
private String score16; private String score16;
/** 二、企业风险准入模块 */
/** 是否存在注销企业信息 */ /** 是否存在注销企业信息 */
@TableField("score_2_1")
private String score21; private String score21;
/** 是否存在执行案件信息 */ /** 是否存在执行案件信息 */
@TableField("score_2_2")
private String score22; private String score22;
/** 是否存在查封信息 */ /** 是否存在查封信息 */
@TableField("score_2_3")
private String score23; private String score23;
/** 是否存在单位未履行生效裁判信息 */ /** 是否存在单位未履行生效裁判信息 */
@TableField("score_2_4")
private String score24; private String score24;
/** 是否存在企业破产清算信息 */ /** 是否存在企业破产清算信息 */
@TableField("score_2_5")
private String score25; private String score25;
/** 是否失信被执行人 */ /** 是否失信被执行人 */
@TableField("score_2_6")
private String score26; private String score26;
/** 是否为诉讼案件被告 */ /** 是否为诉讼案件被告 */
@TableField("score_2_7")
private String score27; private String score27;
/** 三、高管信用评价模块 */
/** 是否存在查封信息(score_3) */ /** 是否存在查封信息(score_3) */
@TableField("score_3_1")
private String score31; private String score31;
/** 是否存在执行案件信息(score_3) */ /** 是否存在执行案件信息(score_3) */
@TableField("score_3_2")
private String score32; private String score32;
/** 是否存在个人未履行生效裁判信息 */ /** 是否存在个人未履行生效裁判信息 */
@TableField("score_3_3")
private String score33; private String score33;
/** 是否存在拖欠工资黑名单(score_3) */ /** 是否存在拖欠工资黑名单(score_3) */
@TableField("score_3_4")
private String score34; private String score34;
/** 是否失信被执行人(score_3) */ /** 是否失信被执行人(score_3) */
@TableField("score_3_5")
private String score35; private String score35;
/** 是否存在刑事案件被告人生效判决信息 */ /** 是否存在刑事案件被告人生效判决信息 */
@TableField("score_3_6")
private String score36; private String score36;
/** 是否为诉讼案件被告(score_3) */ /** 是否为诉讼案件被告(score_3) */
@TableField("score_3_7")
private String score37; private String score37;
/** 四、股东信用评价模块 */
/** 是否存在查封信息(score_4) */ /** 是否存在查封信息(score_4) */
@TableField("score_4_1")
private String score41; private String score41;
/** 是否存在执行案件信息(score_4) */ /** 是否存在执行案件信息(score_4) */
@TableField("score_4_2")
private String score42; private String score42;
/** 是否存在个人未履行生效裁判信息(score_4) */ /** 是否存在个人未履行生效裁判信息(score_4) */
@TableField("score_4_3")
private String score43; private String score43;
/** 是否存在拖欠工资黑名单(score_4) */ /** 是否存在拖欠工资黑名单(score_4) */
@TableField("score_4_4")
private String score44; private String score44;
/** 是否失信被执行人(score_4) */ /** 是否失信被执行人(score_4) */
@TableField("score_4_5")
private String score45; private String score45;
/** 是否存在刑事案件被告人生效判决信息(score_4) */ /** 是否存在刑事案件被告人生效判决信息(score_4) */
@TableField("score_4_6")
private String score46; private String score46;
/** 是否为诉讼案件被告(score_4) */ /** 是否为诉讼案件被告(score_4) */
@TableField("score_4_7")
private String score47; private String score47;
/** 是否存在企业未履行生效裁判信息 */ /** 是否存在企业未履行生效裁判信息 */
@TableField("score_4_8")
private String score48; private String score48;
/** 五、企业社会贡献度模块 */
/** 前12个月纳税金额 */ /** 前12个月纳税金额 */
@TableField("score_5_1")
private String score51; private String score51;
/** 纳税等级 */ /** 纳税等级 */
@TableField("score_5_2")
private String score52; private String score52;
/** 缴纳社保人数 */ /** 缴纳社保人数 */
@TableField("score_5_3")
private String score53; private String score53;
/** 公积金缴纳人数 */ /** 公积金缴纳人数 */
@TableField("score_5_4")
private String score54; private String score54;
/** 是否为出口退税生产清单企业 */ /** 是否为出口退税生产清单企业 */
@TableField("score_5_5")
private String score55; private String score55;
/** 六、企业稳定经营模块 */
/** 市场主体经营年限 */ /** 市场主体经营年限 */
@TableField("score_6_1")
private String score61; private String score61;
/** 股东(或发起人)或投资人信息认缴出资人数 */ /** 股东(或发起人)或投资人信息认缴出资人数 */
@TableField("score_6_2")
private String score62; private String score62;
/** 最大股东持股占比 */ /** 最大股东持股占比 */
@TableField("score_6_3")
private String score63; private String score63;
/** 近三年法定代表人变更次数 */ /** 近三年法定代表人变更次数 */
@TableField("score_6_4")
private String score64; private String score64;
/** 近三年股东变更次数 */ /** 近三年股东变更次数 */
@TableField("score_6_5")
private String score65; private String score65;
/** 近三年经营范围变更次数 */ /** 近三年经营范围变更次数 */
@TableField("score_6_6")
private String score66; private String score66;
/** 近三年经营地址变更次数 */ /** 近三年经营地址变更次数 */
@TableField("score_6_7")
private String score67; private String score67;
/** 近三年缴税年数 */ /** 近三年缴税年数 */
@TableField("score_6_8")
private String score68; private String score68;
/** 法人户籍 */ /** 法人户籍 */
@TableField("score_6_9")
private String score69; private String score69;
/** 法人婚姻状况 */ /** 法人婚姻状况 */
@TableField("score_6_10")
private String score610; private String score610;
/** 七、企业经营能力模块 */
/** 上年增值税金额 */ /** 上年增值税金额 */
@TableField("score_7_1")
private String score71; private String score71;
/** 今年增值税同比变动 */ /** 今年增值税同比变动 */
@TableField("score_7_2")
private String score72; private String score72;
/** 上年企业所得税金额 */ /** 上年企业所得税金额 */
@TableField("score_7_3")
private String score73; private String score73;
/** 今年所得税同比变动 */ /** 今年所得税同比变动 */
@TableField("score_7_4")
private String score74; private String score74;
/** 缴纳社保人数同比变动 */ /** 缴纳社保人数同比变动 */
@TableField("score_7_5")
private String score75; private String score75;
/** 公积金缴纳人数同比变动 */ /** 公积金缴纳人数同比变动 */
@TableField("score_7_6")
private String score76; private String score76;
/** 当年纳税金额同比变动 */ /** 当年纳税金额同比变动 */
@TableField("score_7_7")
private String score77; private String score77;
/** 上年出口退税金额 */ /** 上年出口退税金额 */
@TableField("score_7_8")
private String score78; private String score78;
/** 八、企业偿债能力模块 */
/** 房产套数 */ /** 房产套数 */
@TableField("score_8_1")
private String score81; private String score81;
/** 房产面积 */ /** 房产面积 */
@TableField("score_8_2")
private String score82; private String score82;
/** 未抵押房产套数 */ /** 未抵押房产套数 */
@TableField("score_8_3")
private String score83; private String score83;
/** 未抵押房产面积 */ /** 未抵押房产面积 */
@TableField("score_8_4")
private String score84; private String score84;
/** 已抵押房产被担保债权数额 */ /** 已抵押房产被担保债权数额 */
@TableField("score_8_5")
private String score85; private String score85;
/** 企业车产金额 */ /** 企业车产金额 */
@TableField("score_8_6")
private String score86; private String score86;
/** 九、潜在代偿资源模块 */
/** 法人及股东房产面积合计 */ /** 法人及股东房产面积合计 */
@TableField("score_9_1")
private String score91; private String score91;
/** 法人及股东房产套数合计 */ /** 法人及股东房产套数合计 */
@TableField("score_9_2")
private String score92; private String score92;
/** 法人及股东未抵押房产套数合计 */ /** 法人及股东未抵押房产套数合计 */
@TableField("score_9_3")
private String score93; private String score93;
/** 法人及股东未抵押房产面积 */ /** 法人及股东未抵押房产面积 */
@TableField("score_9_4")
private String score94; private String score94;
/** 法人及股东已抵押房产被担保债权数额合计 */ /** 法人及股东已抵押房产被担保债权数额合计 */
@TableField("score_9_5")
private String score95; private String score95;
/** 法人代表车产金额 */ /** 法人代表车产金额 */
@TableField("score_9_6")
private String score96; private String score96;
/** 十、企业环境信用模块 */
/** 生态信用扣分 */ /** 生态信用扣分 */
@TableField("score_b015")
private String scoreB015; private String scoreB015;
/** 列入环境失信黑名单时间 */ /** 列入环境失信黑名单时间 */
@TableField("score_b016_time")
private String scoreB016Time; private String scoreB016Time;
/** 列入环境失信黑名单原因 */ /** 列入环境失信黑名单原因 */
@TableField("score_b016_reason")
private String scoreB016Reason; private String scoreB016Reason;
/** 环境行为信用评价等级 */ /** 环境行为信用评价等级 */
@TableField("score_a020")
private String scoreA020; private String scoreA020;
} }

View File

@@ -148,15 +148,15 @@ public class CustInfoBusinessServiceImpl implements ICustInfoBusinessService
custInfoBusinessVo.setTagManual(TreeNode.convertToTreeByParentId(tagCreateVos)); custInfoBusinessVo.setTagManual(TreeNode.convertToTreeByParentId(tagCreateVos));
// 仅当登录机构为825时查询九维画像数据 // 仅当登录机构为825时查询九维画像数据
// if ("825".equals(getHeadId())) { if ("965".equals(getHeadId())) {
// LambdaQueryWrapper<Ent9vPortraitOrc> portraitWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<Ent9vPortraitOrc> portraitWrapper = new LambdaQueryWrapper<>();
// portraitWrapper.eq(Ent9vPortraitOrc::getUniscid, custInfoBusiness.getSocialCreditCode()); portraitWrapper.eq(Ent9vPortraitOrc::getUniscid, custInfoBusiness.getSocialCreditCode());
// custInfoBusinessVo.setEnt9vPortrait(ent9vPortraitOrcMapper.selectOne(portraitWrapper)); custInfoBusinessVo.setEnt9vPortrait(ent9vPortraitOrcMapper.selectOne(portraitWrapper));
//
// LambdaQueryWrapper<NineVFinalInfoOrc> finalInfoWrapper = new LambdaQueryWrapper<>(); LambdaQueryWrapper<NineVFinalInfoOrc> finalInfoWrapper = new LambdaQueryWrapper<>();
// finalInfoWrapper.eq(NineVFinalInfoOrc::getUniscid, custInfoBusiness.getSocialCreditCode()); finalInfoWrapper.eq(NineVFinalInfoOrc::getUniscid, custInfoBusiness.getSocialCreditCode());
// custInfoBusinessVo.setNineVFinalInfo(nineVFinalInfoOrcMapper.selectOne(finalInfoWrapper)); custInfoBusinessVo.setNineVFinalInfo(nineVFinalInfoOrcMapper.selectOne(finalInfoWrapper));
// } }
} }
return custInfoBusinessVo; return custInfoBusinessVo;
} }

View File

@@ -857,6 +857,28 @@
select id,cust_name from cust_info_business where social_credit_code = #{socialCreditCode} select id,cust_name from cust_info_business where social_credit_code = #{socialCreditCode}
</select> </select>
<select id="selectExistingSocialCreditCodes" resultType="java.lang.String">
select social_credit_code
from cust_info_business_${deptCode}
where social_credit_code in
<foreach item="socialCreditCode" collection="socialCreditCodes" open="(" separator="," close=")">
#{socialCreditCode}
</foreach>
</select>
<update id="batchUpdateCustLevelBySocialCreditCode">
update cust_info_business_${deptCode}
set cust_level = case social_credit_code
<foreach item="item" collection="list">
when #{item.socialCreditCode} then #{item.custLevel}
</foreach>
end
where social_credit_code in
<foreach item="item" collection="list" open="(" separator="," close=")">
#{item.socialCreditCode}
</foreach>
</update>
<insert id="insertCustomersToBusinessByScCode" parameterType="java.util.List"> <insert id="insertCustomersToBusinessByScCode" parameterType="java.util.List">
INSERT INTO cust_info_business INSERT INTO cust_info_business
(cust_id,cust_name, social_credit_code, update_by, update_time, cust_phone, industry, asset, credit) (cust_id,cust_name, social_credit_code, update_by, update_time, cust_phone, industry, asset, credit)
@@ -866,4 +888,4 @@
</foreach> </foreach>
</insert> </insert>
</mapper> </mapper>

View File

@@ -100,6 +100,50 @@ public class SysLoginService
return tokenService.createToken(loginUser); 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);
}
/** /**
* 校验验证码 * 校验验证码
* *

View File

@@ -31,6 +31,12 @@ public class SysNotice extends BaseEntity
/** 公告状态0正常 1关闭 */ /** 公告状态0正常 1关闭 */
private String status; private String status;
/** 可见总行部门ID多选逗号分隔 */
private String deptIds;
/** 当前登录用户所属总行部门ID仅用于查询过滤 */
private String currentHeadDeptId;
public Long getNoticeId() public Long getNoticeId()
{ {
return noticeId; return noticeId;
@@ -84,6 +90,26 @@ public class SysNotice extends BaseEntity
return status; return status;
} }
public String getDeptIds()
{
return deptIds;
}
public void setDeptIds(String deptIds)
{
this.deptIds = deptIds;
}
public String getCurrentHeadDeptId()
{
return currentHeadDeptId;
}
public void setCurrentHeadDeptId(String currentHeadDeptId)
{
this.currentHeadDeptId = currentHeadDeptId;
}
@Override @Override
public String toString() { public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE) return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@@ -92,6 +118,7 @@ public class SysNotice extends BaseEntity
.append("noticeType", getNoticeType()) .append("noticeType", getNoticeType())
.append("noticeContent", getNoticeContent()) .append("noticeContent", getNoticeContent())
.append("status", getStatus()) .append("status", getStatus())
.append("deptIds", getDeptIds())
.append("createBy", getCreateBy()) .append("createBy", getCreateBy())
.append("createTime", getCreateTime()) .append("createTime", getCreateTime())
.append("updateBy", getUpdateBy()) .append("updateBy", getUpdateBy())

View File

@@ -10,6 +10,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="noticeType" column="notice_type" /> <result property="noticeType" column="notice_type" />
<result property="noticeContent" column="notice_content" /> <result property="noticeContent" column="notice_content" />
<result property="status" column="status" /> <result property="status" column="status" />
<result property="deptIds" column="dept_ids" />
<result property="createBy" column="create_by" /> <result property="createBy" column="create_by" />
<result property="createTime" column="create_time" /> <result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" /> <result property="updateBy" column="update_by" />
@@ -18,7 +19,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap> </resultMap>
<sql id="selectNoticeVo"> <sql id="selectNoticeVo">
select notice_id, notice_title, notice_type, cast(notice_content as char) as notice_content, status, create_by, create_time, update_by, update_time, remark select notice_id, notice_title, notice_type, cast(notice_content as char) as notice_content, status, dept_ids, create_by, create_time, update_by, update_time, remark
from sys_notice from sys_notice
</sql> </sql>
@@ -39,6 +40,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="createBy != null and createBy != ''"> <if test="createBy != null and createBy != ''">
AND create_by like concat('%', #{createBy}, '%') AND create_by like concat('%', #{createBy}, '%')
</if> </if>
<if test="currentHeadDeptId != null and currentHeadDeptId != ''">
AND (dept_ids is null or dept_ids = '' or find_in_set(#{currentHeadDeptId}, dept_ids))
</if>
</where> </where>
</select> </select>
@@ -48,6 +52,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="noticeType != null and noticeType != '' ">notice_type, </if> <if test="noticeType != null and noticeType != '' ">notice_type, </if>
<if test="noticeContent != null and noticeContent != '' ">notice_content, </if> <if test="noticeContent != null and noticeContent != '' ">notice_content, </if>
<if test="status != null and status != '' ">status, </if> <if test="status != null and status != '' ">status, </if>
<if test="deptIds != null">dept_ids, </if>
<if test="remark != null and remark != ''">remark,</if> <if test="remark != null and remark != ''">remark,</if>
<if test="createBy != null and createBy != ''">create_by,</if> <if test="createBy != null and createBy != ''">create_by,</if>
create_time create_time
@@ -56,6 +61,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="noticeType != null and noticeType != ''">#{noticeType}, </if> <if test="noticeType != null and noticeType != ''">#{noticeType}, </if>
<if test="noticeContent != null and noticeContent != ''">#{noticeContent}, </if> <if test="noticeContent != null and noticeContent != ''">#{noticeContent}, </if>
<if test="status != null and status != ''">#{status}, </if> <if test="status != null and status != ''">#{status}, </if>
<if test="deptIds != null">#{deptIds}, </if>
<if test="remark != null and remark != ''">#{remark},</if> <if test="remark != null and remark != ''">#{remark},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if> <if test="createBy != null and createBy != ''">#{createBy},</if>
sysdate() sysdate()
@@ -69,6 +75,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="noticeType != null and noticeType != ''">notice_type = #{noticeType}, </if> <if test="noticeType != null and noticeType != ''">notice_type = #{noticeType}, </if>
<if test="noticeContent != null">notice_content = #{noticeContent}, </if> <if test="noticeContent != null">notice_content = #{noticeContent}, </if>
<if test="status != null and status != ''">status = #{status}, </if> <if test="status != null and status != ''">status = #{status}, </if>
<if test="deptIds != null">dept_ids = #{deptIds}, </if>
<if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if> <if test="updateBy != null and updateBy != ''">update_by = #{updateBy},</if>
update_time = sysdate() update_time = sysdate()
</set> </set>
@@ -86,4 +93,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach> </foreach>
</delete> </delete>
</mapper> </mapper>

View File

@@ -1,5 +1,5 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = UAT支行数智管理平台系统 VUE_APP_TITLE = DEV支行数智管理平台系统
# 开发环境配置 # 开发环境配置
ENV = 'development' ENV = 'development'

View File

@@ -1,5 +1,5 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = PRE支行数智管理平台系统 VUE_APP_TITLE = UAT支行数智管理平台系统
NODE_ENV = staging NODE_ENV = staging

View File

@@ -18,7 +18,7 @@
</style> </style>
<script> <script>
// 这里一定要配置ak 否则无法使用 // 这里一定要配置ak 否则无法使用
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM" window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
</script> </script>
<script> <script>
const result = new URLSearchParams(window.location.search) const result = new URLSearchParams(window.location.search)

View File

@@ -18,7 +18,7 @@
</style> </style>
<script> <script>
// 这里一定要配置ak 否则无法使用 // 这里一定要配置ak 否则无法使用
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM" window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
</script> </script>
<script> <script>

View File

@@ -18,7 +18,7 @@
</style> </style>
<script> <script>
// 这里一定要配置ak 否则无法使用 // 这里一定要配置ak 否则无法使用
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM" window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
</script> </script>
<script> <script>
const result = new URLSearchParams(window.location.search) const result = new URLSearchParams(window.location.search)

View File

@@ -23,7 +23,7 @@
</style> </style>
<script> <script>
// 这里一定要配置ak 否则无法使用 // 这里一定要配置ak 否则无法使用
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM" window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
</script> </script>
<script type="text/javascript" src="./script/getscript.js?type=webgl&v=1.0&services=&t=20230529114224"></script> <script type="text/javascript" src="./script/getscript.js?type=webgl&v=1.0&services=&t=20230529114224"></script>
<!-- 引入logisticsgl的sdk --> <!-- 引入logisticsgl的sdk -->

View File

@@ -39,7 +39,7 @@
<script> <script>
const sdk = new QuHuaSdk({ const sdk = new QuHuaSdk({
ak: 'L7KaAZUYPVSD40nYT09rWWgIdZKUesiX', ak: 'L7KaAZUYPVSD40nYT09rWWgIdZKUesiX',
webAk: 't6k6UC2IZR40Un8kkqM4RXlaQb4FulyM', webAk: 'mokVj0S4sGE9av6NBwy8WHY0xnQsucbE',
domId: 'box', domId: 'box',
defaultCenterCity: "杭州市", // 非必填 defaultCenterCity: "杭州市", // 非必填
_baseUrl: window.NODE_ENV === "production" ? "http://64.202.32.20:5001/logisticsWeb-quhua-intranet" : "http://158.234.96.76:5001/logisticsWeb-quhua-intranet", // 固定格式,必填 _baseUrl: window.NODE_ENV === "production" ? "http://64.202.32.20:5001/logisticsWeb-quhua-intranet" : "http://158.234.96.76:5001/logisticsWeb-quhua-intranet", // 固定格式,必填

View File

@@ -19,7 +19,7 @@
</style> </style>
<script> <script>
// 这里一定要配置ak 否则无法使用 // 这里一定要配置ak 否则无法使用
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM" window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
</script> </script>
<script> <script>
const result = new URLSearchParams(window.location.search) const result = new URLSearchParams(window.location.search)

View File

@@ -34,7 +34,7 @@
</style> </style>
<script> <script>
// 这里一定要配置ak 否则无法使用 // 这里一定要配置ak 否则无法使用
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM" window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
</script> </script>
<script> <script>

View File

@@ -19,7 +19,7 @@
</style> </style>
<script> <script>
// 这里一定要配置ak 否则无法使用 // 这里一定要配置ak 否则无法使用
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM" window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
</script> </script>
<script> <script>
const result = new URLSearchParams(window.location.search) const result = new URLSearchParams(window.location.search)

View File

@@ -8,9 +8,10 @@
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<script> <script>
// 这里一定要配置ak 否则无法使用 // 这里一定要配置ak 否则无法使用
window.BMAP_AUTHENTIC_KEY = "t6k6UC2IZR40Un8kkqM4RXlaQb4FulyM" window.BMAP_AUTHENTIC_KEY = "mokVj0S4sGE9av6NBwy8WHY0xnQsucbE"
</script> </script>
<script type="text/javascript" src="<%= VUE_APP_BAIDU_PATH %>"></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 %>" /> <link rel="stylesheet" type="text/css" href="<%= VUE_APP_BAIDU_CSS_PATH %>" />
<script type="text/javascript" src="\baidu\script\index.umd.min.js"></script> <script type="text/javascript" src="\baidu\script\index.umd.min.js"></script>

View File

@@ -91,4 +91,20 @@ export function uploadTag(data) {
data: data, data: data,
isUpload: true isUpload: true
}) })
} }
export function importBusinessCustLevelAsync(data) {
return request({
url: '/system/custBaseInfo/importBusinessCustLevelAsync',
method: 'post',
data,
isUpload: true
})
}
export function getBusinessCustLevelImportStatus(taskId) {
return request({
url: `/system/custBaseInfo/importBusinessCustLevelStatus/${taskId}`,
method: 'get'
})
}

View File

@@ -41,4 +41,12 @@ export function delNotice(noticeId) {
url: '/system/notice/' + noticeId, url: '/system/notice/' + noticeId,
method: 'delete' method: 'delete'
}) })
} }
// 查询总行列表
export function getHeadList() {
return request({
url: '/system/dept/headList',
method: 'get'
})
}

View File

@@ -168,7 +168,8 @@ export default {
notReadCount: 0, notReadCount: 0,
noticeCenterList: [], noticeCenterList: [],
open2: false, open2: false,
downCenterList: [] downCenterList: [],
noticeCenterTimer: null
}; };
}, },
computed: { computed: {
@@ -192,6 +193,12 @@ export default {
}, },
created() { created() {
this.getCenterList(); this.getCenterList();
this.startNoticePolling();
window.addEventListener('notice-center-refresh', this.getCenterList);
},
beforeDestroy() {
this.clearNoticePolling();
window.removeEventListener('notice-center-refresh', this.getCenterList);
}, },
methods: { methods: {
openModal() { openModal() {
@@ -223,6 +230,18 @@ export default {
} }
}); });
}, },
startNoticePolling() {
this.clearNoticePolling();
this.noticeCenterTimer = setInterval(() => {
this.getCenterList();
}, 10000);
},
clearNoticePolling() {
if (this.noticeCenterTimer) {
clearInterval(this.noticeCenterTimer);
this.noticeCenterTimer = null;
}
},
// 下载中心列表 // 下载中心列表
getDownCenterList() { getDownCenterList() {

View File

@@ -237,6 +237,14 @@
@click.stop="updateAreaInfo" @click.stop="updateAreaInfo"
/> />
</el-tooltip> </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-tooltip placement="top" effect="light" content="删除区域">
<el-popconfirm <el-popconfirm
title="确定删除吗?" title="确定删除吗?"
@@ -271,6 +279,13 @@
</div> </div>
</div> </div>
</transition> </transition>
<!-- 查看客户模态框 -->
<customer-modal
ref="customerModal"
cardType="featured"
:detailInfo="areaForm"
:btnType="'2'"
/>
<div class="search-box"> <div class="search-box">
<el-form size="small" class="myForm"> <el-form size="small" class="myForm">
<el-form-item style="width: 130px"> <el-form-item style="width: 130px">
@@ -337,6 +352,7 @@ import { businessBelongList } from '@/views/grid/create/utils'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { Message } from 'element-ui' import { Message } from 'element-ui'
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash'
import CustomerModal from '@/views/grid/map/draw-area/customer-modal.vue'
const polygonOptions = { const polygonOptions = {
strokeColor: '#5E87DB', strokeColor: '#5E87DB',
strokeWeight: 3, strokeWeight: 3,
@@ -355,6 +371,9 @@ const labelOptions = {
} }
export default { export default {
name: 'BMapPolygonEditor', name: 'BMapPolygonEditor',
components: {
CustomerModal
},
props: ['layerInfo'], props: ['layerInfo'],
data() { data() {
return { return {
@@ -1207,6 +1226,12 @@ export default {
this.$store.dispatch('setIsDrawing', true) this.$store.dispatch('setIsDrawing', true)
this.currentPolygon.enableEditing() this.currentPolygon.enableEditing()
}, },
/**
* 查看客户
*/
previewCustomer() {
this.$refs.customerModal.onOpen()
},
setForm(data) { setForm(data) {
this.areaForm = data this.areaForm = data
this.visible = true this.visible = true

View File

@@ -35,7 +35,7 @@ export default {
initSdk(){ initSdk(){
this.sdk = new QuHuaSdk({ this.sdk = new QuHuaSdk({
ak: 'L7KaAZUYPVSD40nYT09rWWgIdZKUesiX', ak: 'L7KaAZUYPVSD40nYT09rWWgIdZKUesiX',
webAk: 't6k6UC2IZR40Un8kkqM4RXlaQb4FulyM', webAk: 'mokVj0S4sGE9av6NBwy8WHY0xnQsucbE',
domId: 'box', domId: 'box',
defaultCenterCity: "杭州市", // 非必填 defaultCenterCity: "杭州市", // 非必填
_baseUrl: "http://158.234.96.76:5001/logisticsWeb-quhua-intranet", // 固定格式,必填 _baseUrl: "http://158.234.96.76:5001/logisticsWeb-quhua-intranet", // 固定格式,必填
@@ -137,7 +137,7 @@ export default {
onChange(){ onChange(){
this.sdk = new QuHuaSdk({ this.sdk = new QuHuaSdk({
ak: 'L7KaAZUYPVSD40nYT09rWWgIdZKUesiX', ak: 'L7KaAZUYPVSD40nYT09rWWgIdZKUesiX',
webAk: 't6k6UC2IZR40Un8kkqM4RXlaQb4FulyM', webAk: 'mokVj0S4sGE9av6NBwy8WHY0xnQsucbE',
domId: 'box', domId: 'box',
defaultCenterCity: "杭州市", // 非必填 defaultCenterCity: "杭州市", // 非必填
_baseUrl: "http://158.234.96.76:5001/logisticsWeb-quhua-intranet", // 固定格式,必填 _baseUrl: "http://158.234.96.76:5001/logisticsWeb-quhua-intranet", // 固定格式,必填

View File

@@ -1024,6 +1024,77 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-collapse-item> </el-collapse-item>
<el-collapse-item v-if="is825" name="11">
<template slot="title">
<p class="common-title">企业九维数据</p>
</template>
<div v-if="ent9vPortrait || nineVFinalInfo" class="nine-dimension-data">
<!-- 企业九维画像 -->
<div v-if="ent9vPortrait" class="nine-dimension-section">
<h4 class="section-title">1.企业九维画像</h4>
<div class="nine-dimension-chart-container">
<div class="radar-chart-wrapper">
<div ref="nineDimensionRadar" class="nine-dimension-radar"></div>
</div>
<div class="nine-dimension-summary">
<div class="summary-item">
<span class="summary-label">综合评分</span>
<div class="summary-content">
<span class="summary-value">{{ formatPortraitDisplay(ent9vPortrait.scoreAll) }}</span>
<span class="summary-unit">分</span>
</div>
</div>
<div class="summary-item">
<span class="summary-label">总分排名</span>
<div class="summary-content">
<span class="summary-value is-rank">{{ formatPortraitDisplay(ent9vPortrait.scoreAllRank) }}</span>
<span class="summary-unit">名</span>
</div>
</div>
</div>
</div>
</div>
<!-- 九维细项指标 -->
<div v-if="nineVFinalInfo" class="nine-dimension-section">
<h4 class="section-title">2.九维细项指标</h4>
<div class="nine-dimension-detail-table">
<table class="detail-table">
<tbody>
<template v-for="group in nineDimensionDetailGroups">
<tr
v-for="(row, rowIndex) in group.rows"
:key="`${group.title}-${rowIndex}`"
>
<td
v-if="rowIndex === 0"
class="category-cell"
:rowspan="group.rows.length"
>
{{ group.title }}
</td>
<template v-for="metric in row">
<td :key="`${group.title}-${metric.key}-label`" class="metric-label-cell">
{{ metric.label }}
</td>
<td :key="`${group.title}-${metric.key}-value`" class="metric-value-cell">
{{ formatNineDimensionValue(metric) }}
</td>
</template>
<template v-if="row.length === 1">
<td class="metric-label-cell is-empty"></td>
<td class="metric-value-cell is-empty"></td>
</template>
</tr>
</template>
</tbody>
</table>
</div>
</div>
</div>
<el-empty v-else description="暂无企业九维数据"></el-empty>
</el-collapse-item>
</el-collapse> </el-collapse>
<el-dialog <el-dialog
title="该客户尚未建档,请先进行营销建档" title="该客户尚未建档,请先进行营销建档"
@@ -1215,7 +1286,120 @@ import { downloadFiles } from '@/utils'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import { cloneDeep, isEmpty } from 'lodash' import { cloneDeep, isEmpty } from 'lodash'
import _ from 'lodash' import _ from 'lodash'
import * as echarts from 'echarts'
import Custom from '../custom.vue' import Custom from '../custom.vue'
const NINE_DIMENSION_DETAIL_CONFIG = [
{
title: '企业合规经营模块',
metrics: [
{ key: 'score11', label: '是否存在经营异常名录信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score12', label: '是否存在严重违法失信企业名单信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score13', label: '企业环境行为信用等级是否为“E”或“D”', valueMap: { '1': '是', '2': '否' } },
{ key: 'score14', label: '是否存在税务重大税收违法黑名单信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score15', label: '是否存在拖欠工资黑名单', valueMap: { '1': '是', '2': '否' } },
{ key: 'score16', label: '是否存在工商吊销企业信息', valueMap: { '1': '是', '2': '否' } }
]
},
{
title: '企业风险准入模块',
metrics: [
{ key: 'score21', label: '是否存在注销企业信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score22', label: '是否存在执行案件信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score23', label: '是否存在查封信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score24', label: '是否存在单位未履行生效裁判信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score25', label: '是否存在企业破产清算信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score26', label: '是否失信被执行人', valueMap: { '1': '是', '2': '否' } },
{ key: 'score27', label: '是否为诉讼案件被告', valueMap: { '1': '是', '2': '否' } }
]
},
{
title: '高管信用评价模块',
metrics: [
{ key: 'score31', label: '是否存在查封信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score32', label: '是否存在执行案件信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score33', label: '是否存在个人未履行生效裁判信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score34', label: '是否存在拖欠工资黑名单', valueMap: { '1': '是', '2': '否' } },
{ key: 'score35', label: '是否失信被执行人', valueMap: { '1': '是', '2': '否' } },
{ key: 'score36', label: '是否存在刑事案件被告人生效判决信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score37', label: '是否为诉讼案件被告', valueMap: { '1': '是', '2': '否' } }
]
},
{
title: '股东信用评价模块',
metrics: [
{ key: 'score41', label: '是否存在查封信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score42', label: '是否存在执行案件信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score43', label: '是否存在个人未履行生效裁判信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score44', label: '是否存在拖欠工资黑名单', valueMap: { '1': '是', '2': '否' } },
{ key: 'score45', label: '是否失信被执行人', valueMap: { '1': '是', '2': '否' } },
{ key: 'score46', label: '是否存在刑事案件被告人生效判决信息', valueMap: { '1': '是', '2': '否' } },
{ key: 'score47', label: '是否为诉讼案件被告', valueMap: { '1': '是', '2': '否' } },
{ key: 'score48', label: '是否存在企业未履行生效裁判信息', valueMap: { '1': '是', '2': '否' } }
]
},
{
title: '企业社会贡献模块',
metrics: [
{ key: 'score51', label: '前12个月纳税金额', valueMap: { '1': '0', '2': '05万元', '3': '5-10万元', '4': '10-50万元', '5': '50-100万元', '6': '100-500万元', '7': '500-1000万元', '8': '1000万元以上' } },
{ key: 'score52', label: '纳税等级', valueMap: { '1': 'A', '2': 'B', '3': 'M', '4': 'C、D', '5': '未评' } },
{ key: 'score53', label: '缴纳社保人数', valueMap: { '1': '0', '2': '3人以内', '3': '3-10人', '4': '10-30人', '5': '30-50人', '6': '50-100人', '7': '100-500人', '8': '500人以上' } },
{ key: 'score54', label: '公积金缴纳人数', valueMap: { '1': '0', '2': '3人以内', '3': '3-10人', '4': '10-30人', '5': '30-50人', '6': '50-100人', '7': '100-500人', '8': '500人以上' } },
{ key: 'score55', label: '是否为出口退税生产清单企业', valueMap: { '1': '是', '2': '否' } }
]
},
{
title: '企业稳定经营模块',
metrics: [
{ key: 'score61', label: '市场主体经营年限', valueMap: { '1': '1年以内', '2': '1-3年', '3': '3-5年', '4': '5-10年', '5': '10年以上' } },
{ key: 'score62', label: '股东(或发起人)或投资人信息认缴出资人数', valueMap: { '1': '1人独资', '2': '2-5人', '3': '5人以上' } },
{ key: 'score63', label: '股东(或发起人)或投资人信息最大股东持股占比', valueMap: { '1': '10%以内(含)', '2': '10%-30%(含)', '3': '30%-50%(含)', '4': '50%-66.6%(含)', '5': '66.6%以上' } },
{ key: 'score64', label: '近三年法定代表人变更次数', valueMap: { '1': '0次', '2': '1次', '3': '2次', '4': '3次及以上' } },
{ key: 'score65', label: '近三年股东变更次数', valueMap: { '1': '0次', '2': '1次', '3': '2次', '4': '3次及以上' } },
{ key: 'score66', label: '近三年经营范围变更次数', valueMap: { '1': '0次', '2': '1次', '3': '2次', '4': '3次及以上' } },
{ key: 'score67', label: '近三年经营地址变更次数', valueMap: { '1': '0次', '2': '1次', '3': '2次', '4': '3次及以上' } },
{ key: 'score68', label: '近三年缴税年数', valueMap: { '1': '0', '2': '1年', '3': '2年', '4': '3年' } },
{ key: 'score69', label: '法人户籍', valueMap: { '1': '户籍本地、原籍本地', '2': '户籍本地、原籍浙江其他地区', '3': '户籍本地、原籍未浙江', '4': '非本地户籍、浙江籍', '5': '非本地户籍、未浙江籍' } },
{ key: 'score610', label: '法人婚姻状况', valueMap: { '1': '未婚', '2': '已婚', '3': '丧偶', '4': '离婚' } }
]
},
{
title: '企业经营能力模块',
metrics: [
{ key: 'score71', label: '上年增值税金额', valueMap: { '1': '0', '2': '05万元', '3': '5-10万元', '4': '10-30万元', '5': '30-50万元', '6': '50-100万元', '7': '100-500万元', '8': '500-1000万元', '9': '1000万元以上' } },
{ key: 'score72', label: '今年增值税同比变动', valueMap: { '1': '0及以下', '2': '05%(含)', '3': '5%10%(含)', '4': '10%-20%(含)', '5': '20%以上' } },
{ key: 'score73', label: '上年企业所得税金额', valueMap: { '1': '0', '2': '05万元', '3': '5-10万元', '4': '10-30万元', '5': '30-50万元', '6': '50-100万元', '7': '100-500万元', '8': '500-1000万元', '9': '1000万元以上' } },
{ key: 'score74', label: '今年所得税同比变动', valueMap: { '1': '0及以下', '2': '05%(含)', '3': '5%10%(含)', '4': '10%-20%(含)', '5': '20%以上' } },
{ key: 'score75', label: '缴纳社保人数同比变动', valueMap: { '1': '0及以下', '2': '05%(含)', '3': '5%10%(含)', '4': '10%-20%(含)', '5': '20%以上' } },
{ key: 'score76', label: '公积金缴纳人数同比变动', valueMap: { '1': '0及以下', '2': '05%(含)', '3': '5%10%(含)', '4': '10%-20%(含)', '5': '20%以上' } },
{ key: 'score77', label: '当年纳税金额同比变动', valueMap: { '1': '0及以下', '2': '05%(含)', '3': '5%10%(含)', '4': '10%-20%(含)', '5': '20%以上' } },
{ key: 'score78', label: '上年出口退税金额', valueMap: { '1': '0', '2': '05万元', '3': '5-10万元', '4': '10-50万元', '5': '50-100万元', '6': '100万元以上' } }
]
},
{
title: '企业偿债能力模块',
metrics: [
{ key: 'score81', label: '房产套数', valueMap: { '1': '0', '2': '1套', '3': '2套', '4': '3套', '5': '4套及以上' } },
{ key: 'score82', label: '房产面积', valueMap: { '1': '0', '2': '0500平方米', '3': '5001000平方米以下', '4': '1000-3000平方米', '5': '3000-5000平方米', '6': '5000-10000平方米', '7': '10000平方米及以上' } },
{ key: 'score83', label: '未抵押房产套数', valueMap: { '1': '0', '2': '1套', '3': '2套', '4': '3套', '5': '4套及以上' } },
{ key: 'score84', label: '未抵押房产面积', valueMap: { '1': '0', '2': '0500平方米', '3': '5001000平方米以下', '4': '1000-3000平方米', '5': '3000-5000平方米', '6': '5000-10000平方米', '7': '10000平方米及以上' } },
{ key: 'score85', label: '已抵押房产被担保债权数额', valueMap: { '1': '0', '2': '0100万元', '3': '100500万元', '4': '5001000万元', '5': '10003000万元', '6': '30005000万元', '7': '500010000万元', '8': '10000万元以上' } },
{ key: 'score86', label: '企业车产金额', valueMap: { '1': '0', '2': '020万元', '3': '2040万元', '4': '4060万元', '5': '6080万元', '6': '80100万元', '7': '100万元以上' } }
]
},
{
title: '潜在代偿资源模块',
metrics: [
{ key: 'score91', label: '法人代表及股东房产面积合计', valueMap: { '1': '0', '2': '0500平方米', '3': '5001000平方米以下', '4': '1000-3000平方米', '5': '3000-5000平方米', '6': '5000-10000平方米', '7': '10000平方米及以上' } },
{ key: 'score92', label: '法人代表及股东房产套数合计', valueMap: { '1': '0', '2': '1套', '3': '2套', '4': '3套', '5': '4套及以上' } },
{ key: 'score93', label: '法人代表及股东未抵押房产套数合计', valueMap: { '1': '0', '2': '1套', '3': '2套', '4': '3套', '5': '4套及以上' } },
{ key: 'score94', label: '法人代表及股东未抵押房产面积', valueMap: { '1': '0', '2': '0500平方米', '3': '5001000平方米以下', '4': '1000-3000平方米', '5': '3000-5000平方米', '6': '5000-10000平方米', '7': '10000平方米及以上' } },
{ key: 'score95', label: '法人代表及股东已抵押房产被担保债权数额合计', valueMap: { '1': '0', '2': '0100万元', '3': '100500万元', '4': '5001000万元', '5': '10003000万元', '6': '30005000万元', '7': '500010000万元', '8': '10000万元以上' } },
{ key: 'score96', label: '法人代表车产金额', valueMap: { '1': '0', '2': '020万元', '3': '2040万元', '4': '4060万元', '5': '6080万元', '6': '80100万元', '7': '100万元以上' } }
]
}
]
export default { export default {
data() { data() {
var validatePhone = (rule, value, callback) => { var validatePhone = (rule, value, callback) => {
@@ -1347,6 +1531,12 @@ export default {
showTagsList:[], showTagsList:[],
addTagName:"", addTagName:"",
newCustTag:[], newCustTag:[],
// 企业九维数据
ent9vPortrait: null,
nineVFinalInfo: null,
nineDimensionRadarChart: null, // 九维画像雷达图实例
nineDimensionRadarResizeObserver: null,
nineDimensionRadarRenderTimer: null,
regAddress: { regAddress: {
lazy: true, lazy: true,
lazyLoad(node, resolve) { lazyLoad(node, resolve) {
@@ -1403,13 +1593,40 @@ export default {
this.managerOptions = [] this.managerOptions = []
this.authUser = '' this.authUser = ''
} }
},
ent9vPortrait: {
handler(newVal) {
if (!newVal) {
this.destroyNineDimensionRadar()
return
}
this.scheduleNineDimensionRadarRender()
},
deep: true
} }
}, },
components: { components: {
Custom Custom
}, },
computed: { computed: {
...mapGetters(['roles', 'userName']), ...mapGetters(['roles', 'userName', 'deptId']),
nineDimensionDetailGroups() {
return NINE_DIMENSION_DETAIL_CONFIG.map(group => ({
title: group.title,
rows: this.buildNineDimensionRows(group.metrics)
}))
},
nineDimensionScores() {
if (!this.ent9vPortrait) {
return []
}
return [1, 2, 3, 4, 5, 6, 7, 8, 9].map(index =>
this.normalizePortraitScore(this.ent9vPortrait[`score${index}`])
)
},
nineDimensionRadarScale() {
return this.getAdaptiveRadarScale(this.nineDimensionScores)
},
isHeadAdmin() { isHeadAdmin() {
return this.roles.includes('headAdmin') return this.roles.includes('headAdmin')
}, },
@@ -1443,6 +1660,9 @@ export default {
// 海宁 // 海宁
is875() { is875() {
return this.userName.slice(0, 3) === '875' return this.userName.slice(0, 3) === '875'
},
is825() {
return String(this.deptId || '').substring(0, 3) === '825'
} }
}, },
created() { created() {
@@ -1464,6 +1684,16 @@ export default {
// systemUserAllTreeUser().then(res => { // systemUserAllTreeUser().then(res => {
// this.secoureOption = res // this.secoureOption = res
// }) // })
window.addEventListener('resize', this.handleRadarResize);
},
activated() {
this.scheduleNineDimensionRadarRender(120)
},
beforeDestroy() {
this.clearNineDimensionRadarRenderTimer()
this.destroyNineDimensionRadarObserver()
this.destroyNineDimensionRadar()
window.removeEventListener('resize', this.handleRadarResize);
}, },
methods: { methods: {
handleAddTag(){ handleAddTag(){
@@ -1591,6 +1821,9 @@ export default {
this.showTagsList=this.tagsList[0].children[0].children||[]; this.showTagsList=this.tagsList[0].children[0].children||[];
} }
this.tagManualList = cloneDeep(res.data.tagManual) || [] this.tagManualList = cloneDeep(res.data.tagManual) || []
// 企业九维数据
this.ent9vPortrait = res.data.ent9vPortrait || null
this.nineVFinalInfo = res.data.nineVFinalInfo || null
} }
}) })
}, },
@@ -2014,6 +2247,381 @@ export default {
this.managerOptions = response.data; this.managerOptions = response.data;
}); });
}, },
normalizePortraitScore(value) {
const numericValue = Number(value)
return Number.isFinite(numericValue) ? numericValue : 0
},
getAdaptiveRadarScale(scores) {
const maxScore = Math.max(...scores, 0)
if (maxScore <= 0) {
return {
max: 10,
splitNumber: 5,
step: 2
}
}
const splitNumber = 5
const roughStep = maxScore / splitNumber
const magnitude = Math.pow(10, Math.floor(Math.log10(roughStep)))
const normalized = roughStep / magnitude
let niceFactor = 10
if (normalized <= 1) {
niceFactor = 1
} else if (normalized <= 2) {
niceFactor = 2
} else if (normalized <= 2.5) {
niceFactor = 2.5
} else if (normalized <= 5) {
niceFactor = 5
}
const step = niceFactor * magnitude
let max = Math.ceil(maxScore / step) * step
if (max <= maxScore) {
max += step
}
return {
max,
splitNumber,
step
}
},
formatPortraitDisplay(value) {
if (value === null || value === undefined || value === '') {
return '-'
}
const numericValue = Number(value)
if (!Number.isFinite(numericValue)) {
return value
}
return Number.isInteger(numericValue) ? `${numericValue}` : numericValue.toFixed(2)
},
clearNineDimensionRadarRenderTimer() {
if (this.nineDimensionRadarRenderTimer) {
clearTimeout(this.nineDimensionRadarRenderTimer)
this.nineDimensionRadarRenderTimer = null
}
},
scheduleNineDimensionRadarRender(delay = 80) {
this.clearNineDimensionRadarRenderTimer()
this.nineDimensionRadarRenderTimer = setTimeout(() => {
this.initNineDimensionRadar()
}, delay)
},
destroyNineDimensionRadar() {
if (this.nineDimensionRadarChart) {
this.nineDimensionRadarChart.dispose();
this.nineDimensionRadarChart = null;
}
},
destroyNineDimensionRadarObserver() {
if (this.nineDimensionRadarResizeObserver) {
this.nineDimensionRadarResizeObserver.disconnect()
this.nineDimensionRadarResizeObserver = null
}
},
initNineDimensionRadarObserver(chartDom) {
if (!chartDom || typeof ResizeObserver === 'undefined' || this.nineDimensionRadarResizeObserver) {
return
}
this.nineDimensionRadarResizeObserver = new ResizeObserver((entries) => {
const targetRect = entries && entries[0] ? entries[0].contentRect : null
if (this.nineDimensionRadarChart) {
this.handleRadarResize()
return
}
if (targetRect && targetRect.width >= 360 && targetRect.height >= 260) {
this.scheduleNineDimensionRadarRender(0)
}
})
this.nineDimensionRadarResizeObserver.observe(chartDom)
if (chartDom.parentElement) {
this.nineDimensionRadarResizeObserver.observe(chartDom.parentElement)
}
},
formatRadarLabel(label, chunkSize = 7) {
if (!label || label.length <= chunkSize) {
return label
}
const segments = []
for (let index = 0; index < label.length; index += chunkSize) {
segments.push(label.slice(index, index + chunkSize))
}
return segments.join('\n')
},
// 九维画像分数字段标签映射
getPortraitLabel(key) {
const labelMap = {
uniscid: '统一社会信用代码',
cstId: '客户内码',
orgNo: '机构号',
score1: '企业合规经营模块',
score2: '企业风险准入模块',
score3: '高管信用评价模块',
score4: '股东信用评价模块',
score5: '企业社会贡献模块',
score6: '企业稳定经营模块',
score7: '企业经营能力模块',
score8: '企业偿债能力模块',
score9: '潜在代偿资源模块',
scoreAll: '九维总分',
scoreAllRank: '九维总分排名',
datDt: '会计日期'
};
return labelMap[key] || key;
},
// 获取九维画像维度名称列表
getNineDimensionLabels() {
return [
'企业合规经营模块',
'企业风险准入模块',
'高管信用评价模块',
'股东信用评价模块',
'企业社会贡献模块',
'企业稳定经营模块',
'企业经营能力模块',
'企业偿债能力模块',
'潜在代偿资源模块'
];
},
// 初始化九维画像雷达图
initNineDimensionRadar() {
if (!this.ent9vPortrait) {
this.destroyNineDimensionRadar()
return;
}
this.$nextTick(() => {
const chartDom = this.$refs.nineDimensionRadar;
if (!chartDom) return;
this.initNineDimensionRadarObserver(chartDom)
const chartWidth = chartDom.clientWidth || 0
const chartHeight = chartDom.clientHeight || 0
if (chartWidth < 360 || chartHeight < 260) {
this.scheduleNineDimensionRadarRender(140)
return
}
const scores = this.nineDimensionScores
const radarScale = this.nineDimensionRadarScale
const dimensionLabels = this.getNineDimensionLabels()
const isCompactChart = chartWidth > 0 && chartWidth < 760
const currentInstance = echarts.getInstanceByDom(chartDom)
if (currentInstance) {
this.nineDimensionRadarChart = currentInstance
} else {
this.destroyNineDimensionRadar()
this.nineDimensionRadarChart = echarts.init(chartDom);
}
const option = {
tooltip: {
trigger: 'item',
backgroundColor: 'rgba(17, 24, 39, 0.92)',
borderColor: 'rgba(99, 102, 241, 0.35)',
borderWidth: 1,
padding: [10, 14],
textStyle: {
color: '#f8fafc',
fontSize: 13
},
formatter: (params) => {
const lines = dimensionLabels.map((name, index) => `${name}: ${this.formatPortraitDisplay(params.value[index])}`)
return lines.join('<br/>')
}
},
radar: {
indicator: dimensionLabels.map(name => ({ name, max: radarScale.max })),
shape: 'polygon',
splitNumber: radarScale.splitNumber,
radius: isCompactChart ? '76%' : '82%',
center: ['50%', '54%'],
name: {
formatter: name => (isCompactChart ? this.formatRadarLabel(name, 6) : name),
textStyle: {
color: '#364152',
fontSize: isCompactChart ? 13 : 14,
fontWeight: 500
}
},
splitLine: {
lineStyle: {
color: 'rgba(122, 108, 255, 0.34)',
width: 1
}
},
splitArea: {
show: true,
areaStyle: {
color: [
'rgba(107, 114, 128, 0.12)',
'rgba(148, 163, 184, 0.1)',
'rgba(203, 213, 225, 0.08)',
'rgba(226, 232, 240, 0.06)',
'rgba(241, 245, 249, 0.04)'
]
}
},
axisLine: {
lineStyle: {
color: 'rgba(148, 163, 184, 0.4)',
width: 1
}
},
axisNameGap: isCompactChart ? 4 : 8
},
series: [
{
name: '企业九维画像',
type: 'radar',
data: [
{
value: scores,
name: '当前企业',
itemStyle: {
color: '#ffbf2f'
},
areaStyle: {
color: 'rgba(255, 191, 47, 0.68)'
},
lineStyle: {
color: '#f5b700',
width: 2.5
},
symbol: 'circle',
symbolSize: 6
}
]
}
],
animationDuration: 650
};
this.nineDimensionRadarChart.setOption(option);
this.handleRadarResize()
setTimeout(() => {
this.handleRadarResize()
}, 120)
});
},
// 处理雷达图窗口大小变化
handleRadarResize() {
if (this.nineDimensionRadarChart) {
this.$nextTick(() => {
this.nineDimensionRadarChart.resize();
})
}
},
buildNineDimensionRows(metrics) {
const rows = []
for (let index = 0; index < metrics.length; index += 2) {
const firstMetric = metrics[index]
const secondMetric = metrics[index + 1]
rows.push([
{
...firstMetric,
value: this.nineVFinalInfo ? this.nineVFinalInfo[firstMetric.key] : null
},
...(secondMetric
? [{
...secondMetric,
value: this.nineVFinalInfo ? this.nineVFinalInfo[secondMetric.key] : null
}]
: [])
])
}
return rows
},
formatNineDimensionValue(metric) {
const value = metric ? metric.value : null
if (value === null || value === undefined || value === '') {
return '-'
}
const rawValue = `${value}`.trim()
const normalizedValue = /^-?\d+(\.0+)?$/.test(rawValue) ? `${Number(rawValue)}` : rawValue
if (metric && metric.valueMap) {
if (Object.prototype.hasOwnProperty.call(metric.valueMap, normalizedValue)) {
return metric.valueMap[normalizedValue]
}
if (Object.prototype.hasOwnProperty.call(metric.valueMap, rawValue)) {
return metric.valueMap[rawValue]
}
}
if (metric && metric.label && metric.label.includes('是否')) {
if (['1', 1, true, 'true', 'TRUE', '是'].includes(value)) {
return '是'
}
if (['2', 2, false, 'false', 'FALSE', '否'].includes(value)) {
return '否'
}
}
return value
},
// 九维详细信息字段标签映射(部分常用字段)
getFinalInfoLabel(key) {
const labelMap = {
uniscid: '统一社会信用代码',
orgNo: '机构号',
// 维度1 - 合规经营
score11: '是否存在经营异常名录信息',
score12: '是否存在严重违法失信企业名单信息',
score13: '企业环境行为信用等级是否为E或D',
score14: '是否存在税务重大税收违法黑名单信息',
score15: '是否存在拖欠工资黑名单',
score16: '是否存在工商吊销企业信息',
// 维度2 - 风险准入
score21: '是否存在注销企业信息',
score22: '是否存在执行案件信息',
score23: '是否存在查封信息',
score24: '是否存在单位未履行生效裁判信息',
score25: '是否存在企业破产清算信息',
score26: '是否失信被执行人',
score27: '是否为诉讼案件被告',
// 维度5 - 社会贡献度
score51: '前12个月纳税金额',
score52: '纳税等级',
score53: '缴纳社保人数',
score54: '公积金缴纳人数',
score55: '是否为出口退税生产清单企业',
// 维度6 - 稳定经营
score61: '市场主体经营年限',
score62: '认缴出资人数',
score63: '最大股东持股占比',
score64: '近三年法定代表人变更次数',
score65: '近三年股东变更次数',
score69: '法人户籍',
score610: '法人婚姻状况',
// 维度7 - 经营能力
score71: '上年增值税金额',
score72: '今年增值税同比变动',
score73: '上年企业所得税金额',
score74: '今年所得税同比变动',
score75: '缴纳社保人数同比变动',
score77: '当年纳税金额同比变动',
score78: '上年出口退税金额',
// 维度8 - 偿债能力
score81: '房产套数',
score82: '房产面积',
score83: '未抵押房产套数',
score84: '未抵押房产面积',
score85: '已抵押房产被担保债权数额',
score86: '企业车产金额'
};
return labelMap[key] || key;
},
} }
} }
</script> </script>
@@ -2405,4 +3013,210 @@ export default {
width: 320px !important; width: 320px !important;
} }
} }
// 企业九维数据样式
.nine-dimension-data {
padding: 20px 0;
background: linear-gradient(180deg, #ffffff 0%, #f8fbff 100%);
border-radius: 14px;
.nine-dimension-section {
margin-bottom: 30px;
&:last-child {
margin-bottom: 0;
}
.section-title {
font-size: 15px;
font-weight: 600;
color: #202938;
margin-bottom: 18px;
padding-bottom: 12px;
border-bottom: 1px solid #edf1f7;
}
.nine-dimension-chart-container {
display: flex;
align-items: stretch;
gap: 30px;
padding: 26px 30px;
justify-content: space-between;
background: linear-gradient(135deg, #ffffff 0%, #fbfcff 100%);
border: 1px solid #e7edf7;
border-radius: 14px;
box-shadow: 0 8px 26px rgba(31, 55, 88, 0.06);
.radar-chart-wrapper {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: stretch;
padding: 4px 18px 4px 0;
border-right: 1px solid #edf1f7;
.nine-dimension-radar {
width: 100%;
max-width: none;
height: 430px;
margin: 0 auto;
}
}
.nine-dimension-summary {
width: 236px;
flex-shrink: 0;
padding: 18px 20px;
background: #fff;
border: 1px solid #edf1f7;
border-radius: 12px;
box-shadow: 0 12px 28px rgba(34, 65, 120, 0.08);
align-self: center;
.summary-item {
display: flex;
flex-direction: column;
gap: 10px;
padding: 16px 0;
border-bottom: 1px solid #edf1f7;
&:last-child {
padding-bottom: 6px;
border-bottom: 0;
}
.summary-label {
display: block;
font-size: 15px;
color: #202938;
font-weight: 600;
}
.summary-content {
display: flex;
align-items: baseline;
justify-content: flex-end;
gap: 8px;
}
.summary-value {
display: block;
font-size: 40px;
line-height: 1;
font-weight: 700;
color: #1f6feb;
letter-spacing: -1px;
&.is-rank {
color: #22c55e;
}
}
.summary-unit {
font-size: 18px;
font-weight: 600;
color: #202938;
}
}
}
}
.nine-dimension-detail-table {
overflow-x: auto;
.detail-table {
width: 100%;
min-width: 980px;
border-collapse: collapse;
table-layout: fixed;
border: 1px solid #ebeef5;
td {
padding: 18px 16px;
border: 1px solid #ebeef5;
font-size: 13px;
line-height: 1.6;
color: #303133;
vertical-align: middle;
word-break: break-word;
}
.category-cell {
width: 184px;
background: #eef3f9;
color: #1f6feb;
font-size: 15px;
font-weight: 700;
text-align: center;
}
.metric-label-cell {
background: #f6f8fb;
font-weight: 600;
}
.metric-value-cell {
width: 120px;
background: #fff;
font-weight: 500;
text-align: center;
}
.is-empty {
background: #fff;
}
}
}
.data-item {
margin-bottom: 12px;
padding: 10px;
background-color: #f5f7fa;
border-radius: 4px;
.data-label {
display: inline-block;
width: 120px;
font-size: 14px;
color: #606266;
font-weight: 500;
}
.data-value {
font-size: 14px;
color: #303133;
}
}
}
}
@media (max-width: 1400px) {
.nine-dimension-data {
.nine-dimension-section {
.nine-dimension-chart-container {
flex-direction: column;
align-items: stretch;
.radar-chart-wrapper {
padding-right: 0;
border-right: 0;
border-bottom: 1px solid #edf1f7;
padding-bottom: 18px;
margin-bottom: 4px;
.nine-dimension-radar {
max-width: 100%;
height: 390px;
}
}
.nine-dimension-summary {
width: 100%;
}
}
}
}
}
</style> </style>

View File

@@ -142,6 +142,29 @@
<el-dropdown-item @click.native="handleExportAll">导出前1000条<i class="quesiton"></i></el-dropdown-item> <el-dropdown-item @click.native="handleExportAll">导出前1000条<i class="quesiton"></i></el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
<template v-if="selectedTab === '2' && is825">
<div class="import-action">
<el-upload
ref="businessImportUploadRef"
class="business-import-upload"
action="#"
:show-file-list="false"
:http-request="requestBusinessCustLevelImport"
:before-upload="beforeBusinessCustLevelUpload"
:disabled="businessImportLoading"
>
<el-button
icon="el-icon-upload2"
type="primary"
style="margin-left: 10px"
:loading="businessImportLoading"
>导入</el-button>
</el-upload>
<el-tooltip placement="top" trigger="hover" content="导入更新公司客户视图分层分类数据" width="200">
<span class="import-question"><i class="el-icon-question"></i></span>
</el-tooltip>
</div>
</template>
<upload-tag style="margin-left: 10px" v-if="selectedTab === '0' && userName.indexOf('931') === 0"></upload-tag> <upload-tag style="margin-left: 10px" v-if="selectedTab === '0' && userName.indexOf('931') === 0"></upload-tag>
</div> </div>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="searchColoumns" <right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="searchColoumns"
@@ -180,7 +203,16 @@
</div> </div>
</template> </template>
<script> <script>
import { getCustomerList, getCustAddressList, getPerIndcList, getGegionList, getDrawList, getVirtualList } from '@/api/grid/mycustomer.js' import {
getCustomerList,
getCustAddressList,
getPerIndcList,
getGegionList,
getDrawList,
getVirtualList,
importBusinessCustLevelAsync,
getBusinessCustLevelImportStatus
} from '@/api/grid/mycustomer.js'
import GroupCheck from './components/group-check' import GroupCheck from './components/group-check'
import GroupCheckMore from './components/group-check-more' import GroupCheckMore from './components/group-check-more'
import UploadTag from './components/uploadTag.vue' import UploadTag from './components/uploadTag.vue'
@@ -288,6 +320,8 @@ export default {
showMoreIndc: true, showMoreIndc: true,
contiKeys: [], contiKeys: [],
iscollapsed: false, iscollapsed: false,
businessImportLoading: false,
businessImportTimer: null,
} }
}, },
watch: { watch: {
@@ -342,6 +376,9 @@ export default {
isOps() { isOps() {
return this.roles.includes('headOps') return this.roles.includes('headOps')
}, },
is825() {
return String(this.deptId || '').substring(0, 3) === '825'
},
// 客户经理 // 客户经理
isCommonManager() { isCommonManager() {
return this.roles.includes('commonManager') return this.roles.includes('commonManager')
@@ -780,6 +817,63 @@ export default {
) )
} }
}, },
beforeBusinessCustLevelUpload(file) {
const fileName = file.name ? file.name.toLowerCase() : ''
const isExcel = fileName.endsWith('.xls') || fileName.endsWith('.xlsx')
if (!isExcel) {
this.$message.warning('请上传Excel文件')
return false
}
return true
},
async requestBusinessCustLevelImport(fileOut) {
const { file } = fileOut
const formData = new FormData()
formData.append('file', file)
this.businessImportLoading = true
this.clearBusinessImportTimer()
try {
const res = await importBusinessCustLevelAsync(formData)
this.$message.success(res.msg || '导入任务已提交,后台正在处理')
if (res.data) {
this.pollBusinessCustLevelImportStatus(res.data)
}
} finally {
this.businessImportLoading = false
if (this.$refs.businessImportUploadRef) {
this.$refs.businessImportUploadRef.clearFiles()
}
}
},
pollBusinessCustLevelImportStatus(taskId) {
this.clearBusinessImportTimer()
this.businessImportTimer = setInterval(async () => {
try {
const res = await getBusinessCustLevelImportStatus(taskId)
const task = res.data
if (!task || task.status === '0') {
return
}
this.clearBusinessImportTimer()
if (task.status === '1') {
window.dispatchEvent(new Event('notice-center-refresh'))
this.$message.success(task.message || '导入成功')
this.getList()
} else if (task.status === '2') {
window.dispatchEvent(new Event('notice-center-refresh'))
this.$message.error(task.message || '导入失败')
}
} catch (error) {
this.clearBusinessImportTimer()
}
}, 3000)
},
clearBusinessImportTimer() {
if (this.businessImportTimer) {
clearInterval(this.businessImportTimer)
this.businessImportTimer = null
}
}
}, },
created() { created() {
const { query } = this.$route; const { query } = this.$route;
@@ -800,6 +894,9 @@ export default {
this.initGetSecondRegionList() this.initGetSecondRegionList()
this.initgetDrawList() this.initgetDrawList()
this.initGetVirtualList() this.initGetVirtualList()
},
beforeDestroy() {
this.clearBusinessImportTimer()
} }
} }
</script> </script>
@@ -1062,6 +1159,26 @@ export default {
} }
} }
.import-action {
display: inline-block;
position: relative;
}
.business-import-upload {
display: inline-block;
}
.import-question {
position: absolute;
top: -2px;
right: -14px;
color: #b9b9b9;
font-size: 13px;
cursor: pointer;
line-height: 1;
z-index: 1;
}
.iframe-wrap { .iframe-wrap {
width: 100%; width: 100%;
height: 500px; height: 500px;

View File

@@ -870,10 +870,6 @@ export default {
}, },
// headId为875时便捷操作只显示快速入门 // headId为875时便捷操作只显示快速入门
filteredOptArr() { filteredOptArr() {
// 当deptId以875开头时只保留快速入门
if (this.deptId && String(this.deptId).startsWith('875')) {
return this.optArr.filter(item => item.name === '快速入门')
}
return this.optArr return this.optArr
} }
}, },
@@ -1424,10 +1420,9 @@ export default {
handleSaveSetting() { handleSaveSetting() {
let arr = this.optArr.map(item => { let arr = this.optArr.map(item => {
return item.name return item.name
}) }).filter(name => name && name.trim() !== '') // 过滤掉空值
console.log(arr, 'arrarrarr') console.log(arr, 'arrarrarr')
updateQuickSelect(arr).then(res => { updateQuickSelect(arr).then(res => {
if (res.code == 200) { if (res.code == 200) {
Message.success(res.data) Message.success(res.data)
this.handleSetting() this.handleSetting()

View File

@@ -83,6 +83,11 @@
<dict-tag :options="dict.type.sys_notice_type" :value="scope.row.noticeType"/> <dict-tag :options="dict.type.sys_notice_type" :value="scope.row.noticeType"/>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="可见总行" align="center" min-width="180" :show-overflow-tooltip="true">
<template slot-scope="scope">
<span>{{ formatHeadNames(scope.row.deptIds) }}</span>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="100"> <el-table-column label="状态" align="center" prop="status" width="100">
<template slot-scope="scope"> <template slot-scope="scope">
<dict-tag :options="dict.type.sys_notice_status" :value="scope.row.status"/> <dict-tag :options="dict.type.sys_notice_status" :value="scope.row.status"/>
@@ -154,6 +159,25 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="24">
<el-form-item label="可见总行" prop="deptIds">
<el-select
v-model="headList"
placeholder="请选择可见总行,不选则全员可见"
multiple
filterable
clearable
style="width: 100%;"
>
<el-option
v-for="item in headOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item label="内容"> <el-form-item label="内容">
<editor v-model="form.noticeContent" :min-height="192"/> <editor v-model="form.noticeContent" :min-height="192"/>
@@ -170,7 +194,7 @@
</template> </template>
<script> <script>
import { listNotice, getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice"; import { listNotice, getNotice, delNotice, addNotice, updateNotice, getHeadList } from "@/api/system/notice";
export default { export default {
name: "Notice", name: "Notice",
@@ -191,6 +215,10 @@ export default {
total: 0, total: 0,
// 公告表格数据 // 公告表格数据
noticeList: [], noticeList: [],
// 总行选项
headOptions: [],
// 已选总行
headList: [],
// 弹出层标题 // 弹出层标题
title: "", title: "",
// 是否显示弹出层 // 是否显示弹出层
@@ -218,6 +246,7 @@ export default {
}, },
created() { created() {
this.getList(); this.getList();
this.getHeadOptions();
}, },
methods: { methods: {
/** 查询公告列表 */ /** 查询公告列表 */
@@ -229,6 +258,16 @@ export default {
this.loading = false; this.loading = false;
}); });
}, },
getHeadOptions() {
getHeadList().then(response => {
if (response.code === 200 && Array.isArray(response.data)) {
this.headOptions = response.data.map(item => ({
label: item.deptName,
value: String(item.deptId)
}));
}
});
},
// 取消按钮 // 取消按钮
cancel() { cancel() {
this.open = false; this.open = false;
@@ -241,8 +280,10 @@ export default {
noticeTitle: undefined, noticeTitle: undefined,
noticeType: undefined, noticeType: undefined,
noticeContent: undefined, noticeContent: undefined,
deptIds: undefined,
status: "0" status: "0"
}; };
this.headList = [];
this.resetForm("form"); this.resetForm("form");
}, },
/** 搜索按钮操作 */ /** 搜索按钮操作 */
@@ -273,6 +314,7 @@ export default {
const noticeId = row.noticeId || this.ids const noticeId = row.noticeId || this.ids
getNotice(noticeId).then(response => { getNotice(noticeId).then(response => {
this.form = response.data; this.form = response.data;
this.headList = response.data.deptIds ? response.data.deptIds.split(',').filter(Boolean) : [];
this.open = true; this.open = true;
this.title = "修改公告"; this.title = "修改公告";
}); });
@@ -281,6 +323,7 @@ export default {
submitForm: function() { submitForm: function() {
this.$refs["form"].validate(valid => { this.$refs["form"].validate(valid => {
if (valid) { if (valid) {
this.form.deptIds = Array.isArray(this.headList) && this.headList.length > 0 ? this.headList.join(',') : '';
if (this.form.noticeId != undefined) { if (this.form.noticeId != undefined) {
updateNotice(this.form).then(response => { updateNotice(this.form).then(response => {
this.$modal.msgSuccess("修改成功"); this.$modal.msgSuccess("修改成功");
@@ -306,6 +349,20 @@ export default {
this.getList(); this.getList();
this.$modal.msgSuccess("删除成功"); this.$modal.msgSuccess("删除成功");
}).catch(() => {}); }).catch(() => {});
},
formatHeadNames(deptIds) {
if (!deptIds) {
return '全员可见';
}
const selectedIds = String(deptIds).split(',').filter(Boolean);
if (!selectedIds.length) {
return '全员可见';
}
const nameMap = this.headOptions.reduce((map, item) => {
map[item.value] = item.label;
return map;
}, {});
return selectedIds.map(id => nameMap[id] || id).join('、');
} }
} }
}; };