Compare commits

...

2 Commits

21 changed files with 1455 additions and 25 deletions

View File

@@ -18,8 +18,10 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
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));
}
@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.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.CustInfoDeleteFromAnchor;
import com.ruoyi.ibs.customerselect.domain.CustInfoUpdateFromAnchor;
@@ -242,4 +243,10 @@ public interface CustInfoBusinessMapper extends BaseMapper<CustInfoBusiness>
public int insertCustomersToBusinessByScCode(List<SysGroupCustomer> sysGroupCustomers);
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;
import com.ruoyi.ibs.customerselect.domain.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -41,4 +42,8 @@ public interface IMyCustomerService {
public CustListSearchVo selectCustomListSearchVo(CustBaseInfo sysCustomerBasedata);
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;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.annotation.DataScope;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.CustInfoBusiness;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
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.vo.GridRelateVO;
import com.ruoyi.ibs.customerselect.mapper.CustInfoBusinessMapper;
@@ -32,9 +38,16 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
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.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
@@ -45,6 +58,10 @@ import java.util.stream.Collectors;
@Service
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
private CustInfoBusinessMapper custInfoBusinessMapper;
@@ -80,6 +97,12 @@ public class MyCustomerServiceImpl implements IMyCustomerService {
@Resource
private RedisCache redisCache;
@Resource
private NotificationService notificationService;
@Resource(name = "excelImportExecutor")
private ExecutorService excelImportExecutor;
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));
}
@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)
{
startPage();
notice.setCurrentHeadDeptId(SecurityUtils.getHeadId() + "000");
List<SysNotice> list = noticeService.selectNoticeList(notice);
return getDataTable(list);
}

View File

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

View File

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

View File

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

View File

@@ -857,6 +857,28 @@
select id,cust_name from cust_info_business where social_credit_code = #{socialCreditCode}
</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 INTO cust_info_business
(cust_id,cust_name, social_credit_code, update_by, update_time, cust_phone, industry, asset, credit)
@@ -866,4 +888,4 @@
</foreach>
</insert>
</mapper>
</mapper>

View File

@@ -79,7 +79,7 @@ spring:
# 地址
host: 116.62.17.81
# 端口默认为6379
port: 6380
port: 6379
# 数据库索引
database: 0
# 密码

View File

@@ -31,6 +31,12 @@ public class SysNotice extends BaseEntity
/** 公告状态0正常 1关闭 */
private String status;
/** 可见总行部门ID多选逗号分隔 */
private String deptIds;
/** 当前登录用户所属总行部门ID仅用于查询过滤 */
private String currentHeadDeptId;
public Long getNoticeId()
{
return noticeId;
@@ -84,6 +90,26 @@ public class SysNotice extends BaseEntity
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
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
@@ -92,6 +118,7 @@ public class SysNotice extends BaseEntity
.append("noticeType", getNoticeType())
.append("noticeContent", getNoticeContent())
.append("status", getStatus())
.append("deptIds", getDeptIds())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.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="noticeContent" column="notice_content" />
<result property="status" column="status" />
<result property="deptIds" column="dept_ids" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
@@ -18,7 +19,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</resultMap>
<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
</sql>
@@ -39,6 +40,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="createBy != null and createBy != ''">
AND create_by like concat('%', #{createBy}, '%')
</if>
<if test="currentHeadDeptId != null and currentHeadDeptId != ''">
AND (dept_ids is null or dept_ids = '' or find_in_set(#{currentHeadDeptId}, dept_ids))
</if>
</where>
</select>
@@ -48,6 +52,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="noticeType != null and noticeType != '' ">notice_type, </if>
<if test="noticeContent != null and noticeContent != '' ">notice_content, </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="createBy != null and createBy != ''">create_by,</if>
create_time
@@ -56,6 +61,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="noticeType != null and noticeType != ''">#{noticeType}, </if>
<if test="noticeContent != null and noticeContent != ''">#{noticeContent}, </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="createBy != null and createBy != ''">#{createBy},</if>
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="noticeContent != null">notice_content = #{noticeContent}, </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>
update_time = sysdate()
</set>
@@ -86,4 +93,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</foreach>
</delete>
</mapper>
</mapper>

View File

@@ -91,4 +91,20 @@ export function uploadTag(data) {
data: data,
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,
method: 'delete'
})
}
}
// 查询总行列表
export function getHeadList() {
return request({
url: '/system/dept/headList',
method: 'get'
})
}

View File

@@ -168,7 +168,8 @@ export default {
notReadCount: 0,
noticeCenterList: [],
open2: false,
downCenterList: []
downCenterList: [],
noticeCenterTimer: null
};
},
computed: {
@@ -192,6 +193,12 @@ export default {
},
created() {
this.getCenterList();
this.startNoticePolling();
window.addEventListener('notice-center-refresh', this.getCenterList);
},
beforeDestroy() {
this.clearNoticePolling();
window.removeEventListener('notice-center-refresh', this.getCenterList);
},
methods: {
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() {

View File

@@ -1024,6 +1024,77 @@
</el-table-column>
</el-table>
</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-dialog
title="该客户尚未建档,请先进行营销建档"
@@ -1215,7 +1286,120 @@ import { downloadFiles } from '@/utils'
import { mapGetters } from 'vuex'
import { cloneDeep, isEmpty } from 'lodash'
import _ from 'lodash'
import * as echarts from 'echarts'
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 {
data() {
var validatePhone = (rule, value, callback) => {
@@ -1347,6 +1531,12 @@ export default {
showTagsList:[],
addTagName:"",
newCustTag:[],
// 企业九维数据
ent9vPortrait: null,
nineVFinalInfo: null,
nineDimensionRadarChart: null, // 九维画像雷达图实例
nineDimensionRadarResizeObserver: null,
nineDimensionRadarRenderTimer: null,
regAddress: {
lazy: true,
lazyLoad(node, resolve) {
@@ -1403,13 +1593,40 @@ export default {
this.managerOptions = []
this.authUser = ''
}
},
ent9vPortrait: {
handler(newVal) {
if (!newVal) {
this.destroyNineDimensionRadar()
return
}
this.scheduleNineDimensionRadarRender()
},
deep: true
}
},
components: {
Custom
},
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() {
return this.roles.includes('headAdmin')
},
@@ -1443,6 +1660,9 @@ export default {
// 海宁
is875() {
return this.userName.slice(0, 3) === '875'
},
is825() {
return String(this.deptId || '').substring(0, 3) === '825'
}
},
created() {
@@ -1464,6 +1684,16 @@ export default {
// systemUserAllTreeUser().then(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: {
handleAddTag(){
@@ -1591,6 +1821,9 @@ export default {
this.showTagsList=this.tagsList[0].children[0].children||[];
}
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;
});
},
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>
@@ -2405,4 +3013,210 @@ export default {
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>

View File

@@ -142,6 +142,29 @@
<el-dropdown-item @click.native="handleExportAll">导出前1000条<i class="quesiton"></i></el-dropdown-item>
</el-dropdown-menu>
</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>
</div>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" :columns="searchColoumns"
@@ -180,7 +203,16 @@
</div>
</template>
<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 GroupCheckMore from './components/group-check-more'
import UploadTag from './components/uploadTag.vue'
@@ -288,6 +320,8 @@ export default {
showMoreIndc: true,
contiKeys: [],
iscollapsed: false,
businessImportLoading: false,
businessImportTimer: null,
}
},
watch: {
@@ -342,6 +376,9 @@ export default {
isOps() {
return this.roles.includes('headOps')
},
is825() {
return String(this.deptId || '').substring(0, 3) === '825'
},
// 客户经理
isCommonManager() {
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() {
const { query } = this.$route;
@@ -800,6 +894,9 @@ export default {
this.initGetSecondRegionList()
this.initgetDrawList()
this.initGetVirtualList()
},
beforeDestroy() {
this.clearBusinessImportTimer()
}
}
</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 {
width: 100%;
height: 500px;

View File

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

View File

@@ -83,6 +83,11 @@
<dict-tag :options="dict.type.sys_notice_type" :value="scope.row.noticeType"/>
</template>
</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">
<template slot-scope="scope">
<dict-tag :options="dict.type.sys_notice_status" :value="scope.row.status"/>
@@ -154,6 +159,25 @@
</el-radio-group>
</el-form-item>
</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-form-item label="内容">
<editor v-model="form.noticeContent" :min-height="192"/>
@@ -170,7 +194,7 @@
</template>
<script>
import { listNotice, getNotice, delNotice, addNotice, updateNotice } from "@/api/system/notice";
import { listNotice, getNotice, delNotice, addNotice, updateNotice, getHeadList } from "@/api/system/notice";
export default {
name: "Notice",
@@ -191,6 +215,10 @@ export default {
total: 0,
// 公告表格数据
noticeList: [],
// 总行选项
headOptions: [],
// 已选总行
headList: [],
// 弹出层标题
title: "",
// 是否显示弹出层
@@ -218,6 +246,7 @@ export default {
},
created() {
this.getList();
this.getHeadOptions();
},
methods: {
/** 查询公告列表 */
@@ -229,6 +258,16 @@ export default {
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() {
this.open = false;
@@ -241,8 +280,10 @@ export default {
noticeTitle: undefined,
noticeType: undefined,
noticeContent: undefined,
deptIds: undefined,
status: "0"
};
this.headList = [];
this.resetForm("form");
},
/** 搜索按钮操作 */
@@ -273,6 +314,7 @@ export default {
const noticeId = row.noticeId || this.ids
getNotice(noticeId).then(response => {
this.form = response.data;
this.headList = response.data.deptIds ? response.data.deptIds.split(',').filter(Boolean) : [];
this.open = true;
this.title = "修改公告";
});
@@ -281,6 +323,7 @@ export default {
submitForm: function() {
this.$refs["form"].validate(valid => {
if (valid) {
this.form.deptIds = Array.isArray(this.headList) && this.headList.length > 0 ? this.headList.join(',') : '';
if (this.form.noticeId != undefined) {
updateNotice(this.form).then(response => {
this.$modal.msgSuccess("修改成功");
@@ -306,6 +349,20 @@ export default {
this.getList();
this.$modal.msgSuccess("删除成功");
}).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('、');
}
}
};