修复流水异常标签展示与导出
This commit is contained in:
@@ -41,6 +41,10 @@ public class CcdiBankStatementExcel {
|
|||||||
@Excel(name = "交易类型")
|
@Excel(name = "交易类型")
|
||||||
private String cashType;
|
private String cashType;
|
||||||
|
|
||||||
|
/** 异常标签 */
|
||||||
|
@Excel(name = "异常标签")
|
||||||
|
private String hitTags;
|
||||||
|
|
||||||
/** 交易金额 */
|
/** 交易金额 */
|
||||||
@Excel(name = "交易金额")
|
@Excel(name = "交易金额")
|
||||||
private BigDecimal displayAmount;
|
private BigDecimal displayAmount;
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ package com.ruoyi.ccdi.project.domain.vo;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流水明细详情VO
|
* 流水明细详情VO
|
||||||
@@ -96,4 +98,7 @@ public class CcdiBankStatementDetailVO {
|
|||||||
|
|
||||||
/** 原始文件上传时间 */
|
/** 原始文件上传时间 */
|
||||||
private Date uploadTime;
|
private Date uploadTime;
|
||||||
|
|
||||||
|
/** 命中异常标签 */
|
||||||
|
private List<CcdiBankStatementHitTagVO> hitTags = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流水命中异常标签VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CcdiBankStatementHitTagVO {
|
||||||
|
|
||||||
|
/** 规则编码 */
|
||||||
|
private String ruleCode;
|
||||||
|
|
||||||
|
/** 规则名称 */
|
||||||
|
private String ruleName;
|
||||||
|
|
||||||
|
/** 风险等级 */
|
||||||
|
private String riskLevel;
|
||||||
|
|
||||||
|
/** 命中原因 */
|
||||||
|
private String reasonDetail;
|
||||||
|
|
||||||
|
/** 流水ID */
|
||||||
|
private Long bankStatementId;
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ package com.ruoyi.ccdi.project.domain.vo;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流水明细列表VO
|
* 流水明细列表VO
|
||||||
@@ -38,4 +40,7 @@ public class CcdiBankStatementListVO {
|
|||||||
|
|
||||||
/** 页面展示金额 */
|
/** 页面展示金额 */
|
||||||
private BigDecimal displayAmount;
|
private BigDecimal displayAmount;
|
||||||
|
|
||||||
|
/** 命中异常标签 */
|
||||||
|
private List<CcdiBankStatementHitTagVO> hitTags = new ArrayList<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.ruoyi.ccdi.project.mapper;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagResult;
|
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagResult;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -20,6 +21,18 @@ public interface CcdiBankTagResultMapper extends BaseMapper<CcdiBankTagResult> {
|
|||||||
*/
|
*/
|
||||||
int deleteByProjectAndModel(@Param("projectId") Long projectId, @Param("modelCode") String modelCode);
|
int deleteByProjectAndModel(@Param("projectId") Long projectId, @Param("modelCode") String modelCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按项目和流水ID批量查询命中的异常标签
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param bankStatementIds 流水ID列表
|
||||||
|
* @return 命中的异常标签列表
|
||||||
|
*/
|
||||||
|
List<CcdiBankStatementHitTagVO> selectStatementTagsByProjectAndStatementIds(
|
||||||
|
@Param("projectId") Long projectId,
|
||||||
|
@Param("bankStatementIds") List<Long> bankStatementIds
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量插入结果
|
* 批量插入结果
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,16 +5,21 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO;
|
|||||||
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
|
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||||
import com.ruoyi.ccdi.project.service.ICcdiBankStatementService;
|
import com.ruoyi.ccdi.project.service.ICcdiBankStatementService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.Collections;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -31,6 +36,9 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
|||||||
@Resource
|
@Resource
|
||||||
private CcdiBankStatementMapper bankStatementMapper;
|
private CcdiBankStatementMapper bankStatementMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiBankTagResultMapper bankTagResultMapper;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CcdiBankStatementFilterOptionsVO getFilterOptions(Long projectId) {
|
public CcdiBankStatementFilterOptionsVO getFilterOptions(Long projectId) {
|
||||||
CcdiBankStatementFilterOptionsVO options = bankStatementMapper.selectFilterOptions(projectId);
|
CcdiBankStatementFilterOptionsVO options = bankStatementMapper.selectFilterOptions(projectId);
|
||||||
@@ -42,7 +50,9 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
|||||||
CcdiBankStatementQueryDTO queryDTO) {
|
CcdiBankStatementQueryDTO queryDTO) {
|
||||||
CcdiBankStatementQueryDTO normalizedQuery = queryDTO == null ? new CcdiBankStatementQueryDTO() : queryDTO;
|
CcdiBankStatementQueryDTO normalizedQuery = queryDTO == null ? new CcdiBankStatementQueryDTO() : queryDTO;
|
||||||
normalizeQuery(normalizedQuery);
|
normalizeQuery(normalizedQuery);
|
||||||
return bankStatementMapper.selectStatementPage(page, normalizedQuery);
|
Page<CcdiBankStatementListVO> result = bankStatementMapper.selectStatementPage(page, normalizedQuery);
|
||||||
|
attachHitTags(result == null ? Collections.emptyList() : result.getRecords(), normalizedQuery.getProjectId());
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -53,12 +63,58 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
|||||||
if (rows == null || rows.isEmpty()) {
|
if (rows == null || rows.isEmpty()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
attachHitTags(rows, normalizedQuery.getProjectId());
|
||||||
return rows.stream().map(this::toExcel).collect(Collectors.toList());
|
return rows.stream().map(this::toExcel).collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId) {
|
public CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId) {
|
||||||
return bankStatementMapper.selectStatementDetailById(bankStatementId);
|
CcdiBankStatementDetailVO detail = bankStatementMapper.selectStatementDetailById(bankStatementId);
|
||||||
|
if (detail == null || detail.getProjectId() == null || detail.getBankStatementId() == null) {
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap(
|
||||||
|
detail.getProjectId(),
|
||||||
|
List.of(detail.getBankStatementId())
|
||||||
|
);
|
||||||
|
detail.setHitTags(new ArrayList<>(hitTagMap.getOrDefault(detail.getBankStatementId(), Collections.emptyList())));
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void attachHitTags(List<CcdiBankStatementListVO> rows, Long projectId) {
|
||||||
|
if (rows == null || rows.isEmpty() || projectId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<Long> bankStatementIds = rows.stream()
|
||||||
|
.map(CcdiBankStatementListVO::getBankStatementId)
|
||||||
|
.filter(item -> item != null)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
if (bankStatementIds.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap(projectId, bankStatementIds);
|
||||||
|
rows.forEach(row -> row.setHitTags(new ArrayList<>(
|
||||||
|
hitTagMap.getOrDefault(row.getBankStatementId(), Collections.emptyList())
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Long, List<CcdiBankStatementHitTagVO>> loadHitTagMap(Long projectId, List<Long> bankStatementIds) {
|
||||||
|
if (projectId == null || bankStatementIds == null || bankStatementIds.isEmpty()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
List<CcdiBankStatementHitTagVO> hitTags =
|
||||||
|
bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(projectId, bankStatementIds);
|
||||||
|
if (hitTags == null || hitTags.isEmpty()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
return hitTags.stream()
|
||||||
|
.filter(item -> item.getBankStatementId() != null)
|
||||||
|
.collect(Collectors.groupingBy(
|
||||||
|
CcdiBankStatementHitTagVO::getBankStatementId,
|
||||||
|
LinkedHashMap::new,
|
||||||
|
Collectors.toList()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void normalizeQuery(CcdiBankStatementQueryDTO queryDTO) {
|
private void normalizeQuery(CcdiBankStatementQueryDTO queryDTO) {
|
||||||
@@ -138,7 +194,19 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
|||||||
excel.setCustomerAccountNo(row.getCustomerAccountNo());
|
excel.setCustomerAccountNo(row.getCustomerAccountNo());
|
||||||
excel.setUserMemo(row.getUserMemo());
|
excel.setUserMemo(row.getUserMemo());
|
||||||
excel.setCashType(row.getCashType());
|
excel.setCashType(row.getCashType());
|
||||||
|
excel.setHitTags(formatHitTags(row.getHitTags()));
|
||||||
excel.setDisplayAmount(row.getDisplayAmount());
|
excel.setDisplayAmount(row.getDisplayAmount());
|
||||||
return excel;
|
return excel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String formatHitTags(List<CcdiBankStatementHitTagVO> hitTags) {
|
||||||
|
if (hitTags == null || hitTags.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return hitTags.stream()
|
||||||
|
.map(CcdiBankStatementHitTagVO::getRuleName)
|
||||||
|
.filter(item -> item != null && !item.isBlank())
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.joining("、"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
<result property="remark" column="remark"/>
|
<result property="remark" column="remark"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
|
|
||||||
|
<resultMap id="CcdiBankStatementHitTagVOResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO">
|
||||||
|
<result property="ruleCode" column="rule_code"/>
|
||||||
|
<result property="ruleName" column="rule_name"/>
|
||||||
|
<result property="riskLevel" column="risk_level"/>
|
||||||
|
<result property="reasonDetail" column="reason_detail"/>
|
||||||
|
<result property="bankStatementId" column="bank_statement_id"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
<delete id="deleteByProjectAndModel">
|
<delete id="deleteByProjectAndModel">
|
||||||
delete from ccdi_bank_statement_tag_result
|
delete from ccdi_bank_statement_tag_result
|
||||||
where project_id = #{projectId}
|
where project_id = #{projectId}
|
||||||
@@ -37,6 +45,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||||||
</if>
|
</if>
|
||||||
</delete>
|
</delete>
|
||||||
|
|
||||||
|
<select id="selectStatementTagsByProjectAndStatementIds" resultMap="CcdiBankStatementHitTagVOResultMap">
|
||||||
|
select
|
||||||
|
rule_code,
|
||||||
|
rule_name,
|
||||||
|
risk_level,
|
||||||
|
reason_detail,
|
||||||
|
bank_statement_id
|
||||||
|
from ccdi_bank_statement_tag_result
|
||||||
|
where project_id = #{projectId}
|
||||||
|
and bank_statement_id is not null
|
||||||
|
and bank_statement_id IN
|
||||||
|
<foreach collection="bankStatementIds" item="item" open="(" separator="," close=")">
|
||||||
|
#{item}
|
||||||
|
</foreach>
|
||||||
|
order by bank_statement_id asc, id asc
|
||||||
|
</select>
|
||||||
|
|
||||||
<insert id="insertBatch" parameterType="java.util.List">
|
<insert id="insertBatch" parameterType="java.util.List">
|
||||||
insert into ccdi_bank_statement_tag_result (
|
insert into ccdi_bank_statement_tag_result (
|
||||||
project_id, model_code, model_name, rule_code, rule_name, indicator_code,
|
project_id, model_code, model_name, rule_code, rule_name, indicator_code,
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.ruoyi.ccdi.project.domain.vo;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
class CcdiBankStatementHitTagsContractTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void listVo_shouldExposeHitTagsField() throws Exception {
|
||||||
|
Field field = CcdiBankStatementListVO.class.getDeclaredField("hitTags");
|
||||||
|
|
||||||
|
assertNotNull(field, "流水列表VO应返回命中异常标签");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void detailVo_shouldExposeHitTagsField() throws Exception {
|
||||||
|
Field field = CcdiBankStatementDetailVO.class.getDeclaredField("hitTags");
|
||||||
|
|
||||||
|
assertNotNull(field, "流水详情VO应返回命中异常标签");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void excelVo_shouldExposeHitTagsField() throws Exception {
|
||||||
|
Field field = com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel.class.getDeclaredField("hitTags");
|
||||||
|
|
||||||
|
assertNotNull(field, "流水导出对象应返回异常标签文本列");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,9 +2,12 @@ package com.ruoyi.ccdi.project.mapper;
|
|||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
class CcdiBankTagResultMapperXmlTest {
|
class CcdiBankTagResultMapperXmlTest {
|
||||||
@@ -20,4 +23,25 @@ class CcdiBankTagResultMapperXmlTest {
|
|||||||
assertTrue(xml.contains("model_code = #{modelCode}"));
|
assertTrue(xml.contains("model_code = #{modelCode}"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapper_shouldExposeStatementTagQueryForBankStatementDetails() {
|
||||||
|
Method method = Arrays.stream(CcdiBankTagResultMapper.class.getDeclaredMethods())
|
||||||
|
.filter(item -> "selectStatementTagsByProjectAndStatementIds".equals(item.getName()))
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
|
||||||
|
assertNotNull(method, "应提供按项目和流水ID批量查询异常标签的方法");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void xml_shouldDefineStatementTagQueryForBankStatementDetails() throws Exception {
|
||||||
|
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
|
||||||
|
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
assertTrue(xml.contains("selectStatementTagsByProjectAndStatementIds"), xml);
|
||||||
|
assertTrue(xml.contains("bank_statement_id IN"), xml);
|
||||||
|
assertTrue(xml.contains("project_id = #{projectId}"), xml);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO;
|
|||||||
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
|
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
|
||||||
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementOptionVO;
|
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementOptionVO;
|
||||||
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
||||||
|
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
@@ -36,6 +38,9 @@ class CcdiBankStatementServiceImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private CcdiBankStatementMapper bankStatementMapper;
|
private CcdiBankStatementMapper bankStatementMapper;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CcdiBankTagResultMapper bankTagResultMapper;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getFilterOptions_shouldReturnProjectWideOptions() {
|
void getFilterOptions_shouldReturnProjectWideOptions() {
|
||||||
CcdiBankStatementFilterOptionsVO options = new CcdiBankStatementFilterOptionsVO();
|
CcdiBankStatementFilterOptionsVO options = new CcdiBankStatementFilterOptionsVO();
|
||||||
@@ -54,6 +59,7 @@ class CcdiBankStatementServiceImplTest {
|
|||||||
queryDTO.setProjectId(100L);
|
queryDTO.setProjectId(100L);
|
||||||
queryDTO.setOrderBy("amount");
|
queryDTO.setOrderBy("amount");
|
||||||
queryDTO.setOrderDirection("desc");
|
queryDTO.setOrderDirection("desc");
|
||||||
|
page.setRecords(List.of(new CcdiBankStatementListVO()));
|
||||||
doReturn(page).when(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO));
|
doReturn(page).when(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO));
|
||||||
|
|
||||||
service.selectStatementPage(page, queryDTO);
|
service.selectStatementPage(page, queryDTO);
|
||||||
@@ -111,14 +117,66 @@ class CcdiBankStatementServiceImplTest {
|
|||||||
assertEquals("6222", result.get(0).getLeAccountNo());
|
assertEquals("6222", result.get(0).getLeAccountNo());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void selectStatementListForExport_shouldIncludeHitTagNames() {
|
||||||
|
CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO();
|
||||||
|
queryDTO.setProjectId(43L);
|
||||||
|
CcdiBankStatementListVO row = new CcdiBankStatementListVO();
|
||||||
|
row.setBankStatementId(51274L);
|
||||||
|
row.setLeAccountNo("6222");
|
||||||
|
when(bankStatementMapper.selectStatementListForExport(same(queryDTO))).thenReturn(List.of(row));
|
||||||
|
|
||||||
|
CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO();
|
||||||
|
hitTag.setBankStatementId(51274L);
|
||||||
|
hitTag.setRuleName("大额存现交易");
|
||||||
|
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(43L, List.of(51274L)))
|
||||||
|
.thenReturn(List.of(hitTag));
|
||||||
|
|
||||||
|
List<CcdiBankStatementExcel> result = service.selectStatementListForExport(queryDTO);
|
||||||
|
|
||||||
|
assertEquals(1, result.size());
|
||||||
|
assertEquals("大额存现交易", result.get(0).getHitTags());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void selectStatementPage_shouldAttachHitTags() {
|
||||||
|
Page<CcdiBankStatementListVO> page = new Page<>(1, 10);
|
||||||
|
CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO();
|
||||||
|
queryDTO.setProjectId(43L);
|
||||||
|
CcdiBankStatementListVO row = new CcdiBankStatementListVO();
|
||||||
|
row.setBankStatementId(51274L);
|
||||||
|
page.setRecords(List.of(row));
|
||||||
|
doReturn(page).when(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO));
|
||||||
|
|
||||||
|
CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO();
|
||||||
|
hitTag.setBankStatementId(51274L);
|
||||||
|
hitTag.setRuleCode("LARGE_CASH_DEPOSIT");
|
||||||
|
hitTag.setRuleName("大额存现交易");
|
||||||
|
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(43L, List.of(51274L)))
|
||||||
|
.thenReturn(List.of(hitTag));
|
||||||
|
|
||||||
|
Page<CcdiBankStatementListVO> result = service.selectStatementPage(page, queryDTO);
|
||||||
|
|
||||||
|
assertEquals(1, result.getRecords().get(0).getHitTags().size());
|
||||||
|
assertEquals("LARGE_CASH_DEPOSIT", result.getRecords().get(0).getHitTags().get(0).getRuleCode());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void getStatementDetail_shouldDelegateToMapper() {
|
void getStatementDetail_shouldDelegateToMapper() {
|
||||||
CcdiBankStatementDetailVO detailVO = new CcdiBankStatementDetailVO();
|
CcdiBankStatementDetailVO detailVO = new CcdiBankStatementDetailVO();
|
||||||
detailVO.setBankStatementId(200L);
|
detailVO.setBankStatementId(200L);
|
||||||
|
detailVO.setProjectId(43L);
|
||||||
when(bankStatementMapper.selectStatementDetailById(200L)).thenReturn(detailVO);
|
when(bankStatementMapper.selectStatementDetailById(200L)).thenReturn(detailVO);
|
||||||
|
CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO();
|
||||||
|
hitTag.setBankStatementId(200L);
|
||||||
|
hitTag.setRuleName("大额存现交易");
|
||||||
|
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(43L, List.of(200L)))
|
||||||
|
.thenReturn(List.of(hitTag));
|
||||||
|
|
||||||
CcdiBankStatementDetailVO result = service.getStatementDetail(200L);
|
CcdiBankStatementDetailVO result = service.getStatementDetail(200L);
|
||||||
|
|
||||||
assertSame(detailVO, result);
|
assertSame(detailVO, result);
|
||||||
|
assertEquals(1, result.getHitTags().size());
|
||||||
|
assertEquals("大额存现交易", result.getHitTags().get(0).getRuleName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# 流水明细异常标签后端实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 流水列表/详情返回异常标签
|
||||||
|
- 导出流水返回异常标签列
|
||||||
|
- 流水标签结果批量查询能力
|
||||||
|
- 后端单元测试补充
|
||||||
|
|
||||||
|
## 实施说明
|
||||||
|
- 新增 `CcdiBankStatementHitTagVO`,统一承载流水命中的异常标签编码、名称、风险等级和命中原因。
|
||||||
|
- 在 `CcdiBankStatementListVO`、`CcdiBankStatementDetailVO` 中新增 `hitTags` 字段,作为流水列表页和详情弹窗的统一返回结构。
|
||||||
|
- 在 `CcdiBankStatementExcel` 中新增“异常标签”导出列,导出时将命中的标签名称按顺序拼接为文本。
|
||||||
|
- 在 `CcdiBankTagResultMapper` 中新增按 `projectId + bankStatementIds` 批量查询命中标签的方法,并在 XML 中补充对应 SQL。
|
||||||
|
- 在 `CcdiBankStatementServiceImpl` 中补充标签挂载逻辑:
|
||||||
|
- 列表查询完成后,按当前页流水 ID 批量回查标签并分组挂载到每条记录。
|
||||||
|
- 详情查询完成后,按当前流水 ID 回查标签并挂载到详情对象。
|
||||||
|
- 导出查询完成后,复用同一套标签回查逻辑,将命中标签名称写入 Excel 导出对象。
|
||||||
|
- 本次未改动流水标签判定规则,只修复“标签结果已入库但接口未返回”的数据链路缺口。
|
||||||
|
|
||||||
|
## 问题定位结论
|
||||||
|
- 数据库中 `project_id = 43`、摘要为 `ATM现金存款` 的流水记录已存在 `LARGE_CASH_DEPOSIT` 等命中结果。
|
||||||
|
- 原因是 `ccdi_bank_statement` 列表接口与详情接口此前未将 `ccdi_bank_statement_tag_result` 的命中标签组装到返回 VO 中,导致前端无法展示。
|
||||||
|
|
||||||
|
## 验证执行
|
||||||
|
- 执行 `mvn -pl ccdi-project test -Dtest=CcdiBankTagResultMapperXmlTest,CcdiBankStatementHitTagsContractTest,CcdiBankStatementServiceImplTest`,结果通过。
|
||||||
|
- 执行 `mvn -pl ccdi-project test -Dtest=CcdiBankStatementHitTagsContractTest,CcdiBankStatementServiceImplTest,CcdiBankStatementControllerTest`,结果通过。
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# 流水明细异常标签前端实施记录
|
||||||
|
|
||||||
|
## 修改内容
|
||||||
|
- 列表异常标签列
|
||||||
|
- 详情异常标签模块
|
||||||
|
- 静态测试与构建验证
|
||||||
|
|
||||||
|
## 实施说明
|
||||||
|
- 在 `DetailQuery.vue` 的列表表格中新增“异常标签”列,仅展示 `hitTags` 中的标签名称,并根据风险等级映射标签颜色。
|
||||||
|
- 在详情弹窗中新增“命中异常标签”模块,展示标签名称、风险等级中文文案和命中原因摘要。
|
||||||
|
- 列表接口返回值与详情接口返回值统一对 `hitTags` 做数组归一化,避免后端返回 `null` 时影响空态展示。
|
||||||
|
- 为空标签场景补充统一空态文案,保持列表和详情展示一致。
|
||||||
|
|
||||||
|
## 验证执行
|
||||||
|
- 执行 `node tests/unit/detail-query-filter-layout.test.js`,结果通过。
|
||||||
|
- 执行 `node tests/unit/detail-query-detail-dialog.test.js`,结果通过。
|
||||||
|
- 执行 `node tests/unit/detail-query-hit-tags-list.test.js`,结果通过。
|
||||||
|
- 执行 `npm run build:prod`,结果通过,存在项目既有体积告警,但无新增编译错误。
|
||||||
|
|
||||||
|
## 接口说明
|
||||||
|
- 本次未修改 `ruoyi-ui/src/api/ccdiProjectBankStatement.js`。
|
||||||
|
- 原因:继续复用现有列表与详情接口,仅消费后端扩展后的响应数据结构,不新增前端请求入口。
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# 流水明细异常标签后端验证记录
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 流水列表返回 `hitTags`
|
||||||
|
- 流水详情返回 `hitTags`
|
||||||
|
- 导出流水返回异常标签列
|
||||||
|
- 标签结果 Mapper 查询能力
|
||||||
|
- 已入库标签结果的链路核对
|
||||||
|
|
||||||
|
## 数据核对
|
||||||
|
- 2026-03-19 查询 `ccdi_bank_statement`:`project_id = 43` 下存在摘要为 `ATM现金存款` 的流水记录 `bank_statement_id=51274`、`bank_statement_id=49342`。
|
||||||
|
- 2026-03-19 查询 `ccdi_bank_statement_tag_result`:上述两条流水均存在 `LARGE_CASH_DEPOSIT`、`SINGLE_LARGE_INCOME` 命中结果。
|
||||||
|
|
||||||
|
## 自动验证
|
||||||
|
- 2026-03-19 执行 `mvn -pl ccdi-project test -Dtest=CcdiBankTagResultMapperXmlTest,CcdiBankStatementHitTagsContractTest,CcdiBankStatementServiceImplTest`,结果:通过
|
||||||
|
- 2026-03-19 执行 `mvn -pl ccdi-project test -Dtest=CcdiBankStatementHitTagsContractTest,CcdiBankStatementServiceImplTest,CcdiBankStatementControllerTest`,结果:通过
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- [x] 列表查询结果可挂载命中异常标签
|
||||||
|
- [x] 详情查询结果可挂载命中异常标签
|
||||||
|
- [x] 导出对象包含“异常标签”文本列
|
||||||
|
- [x] 标签结果 Mapper 支持按项目和流水 ID 批量回查
|
||||||
|
- [x] `ATM现金存款` 对应流水的标签数据已在库内存在
|
||||||
|
|
||||||
|
## 联调建议
|
||||||
|
- 前端刷新 `project_id=43` 的流水明细页面后,重点核对 `bank_statement_id=51274`、`bank_statement_id=49342` 是否显示“大额存现交易”等异常标签。
|
||||||
|
- 使用相同筛选条件执行“导出流水”,核对导出文件中的“异常标签”列是否包含“大额存现交易”等标签名称。
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# 流水明细异常标签前端验证记录
|
||||||
|
|
||||||
|
## 验证范围
|
||||||
|
- 列表异常标签列
|
||||||
|
- 详情异常标签模块
|
||||||
|
- 空态展示
|
||||||
|
- 导出入口回归
|
||||||
|
|
||||||
|
## 自动验证
|
||||||
|
- 2026-03-19 执行 `node tests/unit/detail-query-filter-layout.test.js`,结果:通过
|
||||||
|
- 2026-03-19 执行 `node tests/unit/detail-query-detail-dialog.test.js`,结果:通过
|
||||||
|
- 2026-03-19 执行 `node tests/unit/detail-query-hit-tags-list.test.js`,结果:通过
|
||||||
|
- 2026-03-19 执行 `npm run build:prod`,结果:通过
|
||||||
|
|
||||||
|
## 验证结果
|
||||||
|
- [x] 列表显示命中标签名称
|
||||||
|
- [x] 详情显示名称、风险等级、命中原因摘要
|
||||||
|
- [x] 无标签时显示空态
|
||||||
|
- [ ] 导出入口仍可触发下载
|
||||||
|
|
||||||
|
## 联调待确认
|
||||||
|
- 点击“导出流水”按钮后是否仍能正常触发下载
|
||||||
@@ -227,6 +227,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
<el-table-column label="异常标签" min-width="220">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<div v-if="scope.row.hitTags && scope.row.hitTags.length" class="hit-tag-list">
|
||||||
|
<el-tag
|
||||||
|
v-for="(tag, index) in scope.row.hitTags"
|
||||||
|
:key="`${scope.row.bankStatementId}-tag-${index}`"
|
||||||
|
size="mini"
|
||||||
|
:type="mapRiskLevelToTagType(tag.riskLevel)"
|
||||||
|
effect="plain"
|
||||||
|
>
|
||||||
|
{{ tag.ruleName }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
<span v-else class="empty-text">-</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
label="交易金额"
|
label="交易金额"
|
||||||
prop="displayAmount"
|
prop="displayAmount"
|
||||||
@@ -340,6 +356,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="detail-hit-tag-section">
|
||||||
|
<div class="detail-section-title">命中异常标签</div>
|
||||||
|
<div
|
||||||
|
v-if="detailData.hitTags && detailData.hitTags.length"
|
||||||
|
class="detail-hit-tag-items"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(tag, index) in detailData.hitTags"
|
||||||
|
:key="`detail-tag-${index}`"
|
||||||
|
class="detail-hit-tag-item"
|
||||||
|
>
|
||||||
|
<div class="detail-hit-tag-header">
|
||||||
|
<span class="detail-hit-tag-name">{{ formatField(tag.ruleName) }}</span>
|
||||||
|
<el-tag size="mini" :type="mapRiskLevelToTagType(tag.riskLevel)" effect="plain">
|
||||||
|
{{ formatRiskLevel(tag.riskLevel) }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="detail-hit-tag-reason">{{ formatField(tag.reasonDetail) }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="detail-hit-tag-empty">当前流水未命中异常标签</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div slot="footer" class="detail-dialog-footer">
|
<div slot="footer" class="detail-dialog-footer">
|
||||||
<el-button @click="closeDetailDialog">取消</el-button>
|
<el-button @click="closeDetailDialog">取消</el-button>
|
||||||
@@ -396,6 +434,8 @@ const createEmptyOptionData = () => ({
|
|||||||
ourAccountOptions: [],
|
ourAccountOptions: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const normalizeHitTags = (hitTags) => (Array.isArray(hitTags) ? hitTags : []);
|
||||||
|
|
||||||
const createEmptyDetailData = () => ({
|
const createEmptyDetailData = () => ({
|
||||||
bankStatementId: "",
|
bankStatementId: "",
|
||||||
projectId: "",
|
projectId: "",
|
||||||
@@ -427,6 +467,7 @@ const createEmptyDetailData = () => ({
|
|||||||
uploadTime: "",
|
uploadTime: "",
|
||||||
sourceFileName: "",
|
sourceFileName: "",
|
||||||
fileName: "",
|
fileName: "",
|
||||||
|
hitTags: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -489,7 +530,10 @@ export default {
|
|||||||
this.loading = true;
|
this.loading = true;
|
||||||
try {
|
try {
|
||||||
const res = await listBankStatement(this.queryParams);
|
const res = await listBankStatement(this.queryParams);
|
||||||
this.list = res.rows || [];
|
this.list = (res.rows || []).map((item) => ({
|
||||||
|
...item,
|
||||||
|
hitTags: normalizeHitTags(item && item.hitTags),
|
||||||
|
}));
|
||||||
this.total = res.total || 0;
|
this.total = res.total || 0;
|
||||||
this.listError = "";
|
this.listError = "";
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -575,9 +619,11 @@ export default {
|
|||||||
this.detailLoading = true;
|
this.detailLoading = true;
|
||||||
try {
|
try {
|
||||||
const res = await getBankStatementDetail(row.bankStatementId);
|
const res = await getBankStatementDetail(row.bankStatementId);
|
||||||
|
const detail = res.data || {};
|
||||||
this.detailData = {
|
this.detailData = {
|
||||||
...createEmptyDetailData(),
|
...createEmptyDetailData(),
|
||||||
...(res.data || {}),
|
...detail,
|
||||||
|
hitTags: normalizeHitTags(detail.hitTags),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.detailData = createEmptyDetailData();
|
this.detailData = createEmptyDetailData();
|
||||||
@@ -665,6 +711,29 @@ export default {
|
|||||||
}
|
}
|
||||||
return this.formatDate(detail.uploadTime);
|
return this.formatDate(detail.uploadTime);
|
||||||
},
|
},
|
||||||
|
formatRiskLevel(value) {
|
||||||
|
const level = String(value || "").toUpperCase();
|
||||||
|
if (level === "HIGH") {
|
||||||
|
return "高风险";
|
||||||
|
}
|
||||||
|
if (level === "MEDIUM") {
|
||||||
|
return "中风险";
|
||||||
|
}
|
||||||
|
if (level === "LOW") {
|
||||||
|
return "低风险";
|
||||||
|
}
|
||||||
|
return "未标注";
|
||||||
|
},
|
||||||
|
mapRiskLevelToTagType(riskLevel) {
|
||||||
|
const level = String(riskLevel || "").toUpperCase();
|
||||||
|
if (level === "HIGH") {
|
||||||
|
return "danger";
|
||||||
|
}
|
||||||
|
if (level === "MEDIUM") {
|
||||||
|
return "warning";
|
||||||
|
}
|
||||||
|
return "info";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -821,6 +890,18 @@ export default {
|
|||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.hit-tag-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.amount-text {
|
.amount-text {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@@ -841,6 +922,7 @@ export default {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||||
gap: 24px 32px;
|
gap: 24px 32px;
|
||||||
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-field {
|
.detail-field {
|
||||||
@@ -900,6 +982,60 @@ export default {
|
|||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-hit-tag-section {
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
padding-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section-title {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-hit-tag-items {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-hit-tag-item {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-hit-tag-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-hit-tag-name {
|
||||||
|
color: #303133;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 22px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-hit-tag-reason {
|
||||||
|
margin-top: 8px;
|
||||||
|
color: #606266;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-hit-tag-empty {
|
||||||
|
color: #909399;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.detail-dialog-footer {
|
.detail-dialog-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -975,6 +1111,11 @@ export default {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-hit-tag-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.detail-dialog) {
|
:deep(.detail-dialog) {
|
||||||
width: calc(100vw - 24px) !important;
|
width: calc(100vw - 24px) !important;
|
||||||
margin-top: 8vh !important;
|
margin-top: 8vh !important;
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ assert(
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
[
|
||||||
|
"命中异常标签",
|
||||||
|
"detail-hit-tag-section",
|
||||||
|
"detailData.hitTags",
|
||||||
|
"当前流水未命中异常标签",
|
||||||
|
"mapRiskLevelToTagType(tag.riskLevel)",
|
||||||
|
].forEach((token) => {
|
||||||
|
assert(source.includes(token), `详情弹窗缺少异常标签结构: ${token}`);
|
||||||
|
});
|
||||||
|
|
||||||
const tableBlockMatch = source.match(/<el-table[\s\S]*?class="result-table"[\s\S]*?>/m);
|
const tableBlockMatch = source.match(/<el-table[\s\S]*?class="result-table"[\s\S]*?>/m);
|
||||||
assert(tableBlockMatch, "未找到流水明细列表表格");
|
assert(tableBlockMatch, "未找到流水明细列表表格");
|
||||||
assert(
|
assert(
|
||||||
|
|||||||
19
ruoyi-ui/tests/unit/detail-query-hit-tags-list.test.js
Normal file
19
ruoyi-ui/tests/unit/detail-query-hit-tags-list.test.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
const assert = require("assert");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const componentPath = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"../../src/views/ccdiProject/components/detail/DetailQuery.vue"
|
||||||
|
);
|
||||||
|
const source = fs.readFileSync(componentPath, "utf8");
|
||||||
|
|
||||||
|
assert(source.includes('label="异常标签"'), "列表应新增异常标签列");
|
||||||
|
assert(source.includes("scope.row.hitTags"), "异常标签列应读取当前行的 hitTags");
|
||||||
|
assert(
|
||||||
|
source.includes('v-for="(tag, index) in scope.row.hitTags"'),
|
||||||
|
"异常标签列应逐个渲染命中标签"
|
||||||
|
);
|
||||||
|
assert(source.includes("tag.ruleName"), "异常标签列应展示标签名称");
|
||||||
|
|
||||||
|
console.log("detail-query-hit-tags-list test passed");
|
||||||
Reference in New Issue
Block a user