Compare commits
9 Commits
d45e9410ef
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| ce66dc3ba8 | |||
| f3c1e2ea93 | |||
| 87b2352001 | |||
| 999350265b | |||
| f8ee1ecf1c | |||
| 35467fd361 | |||
| 64cb847db3 | |||
| bf290c509c | |||
| c5b2033a3d |
@@ -272,9 +272,11 @@ public interface CcdiBankTagAnalysisMapper {
|
||||
* 微信支付宝提现超额
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param amountThreshold 提现金额阈值
|
||||
* @return 对象命中结果
|
||||
*/
|
||||
List<BankTagObjectHitVO> selectWithdrawAmtObjects(@Param("projectId") Long projectId);
|
||||
List<BankTagObjectHitVO> selectWithdrawAmtObjects(@Param("projectId") Long projectId,
|
||||
@Param("amountThreshold") BigDecimal amountThreshold);
|
||||
|
||||
/**
|
||||
* 工资快速转出
|
||||
|
||||
@@ -20,6 +20,10 @@ public interface CcdiFundGraphMapper {
|
||||
|
||||
List<CcdiFundGraphNodeVO> selectFundGraphSubjects(@Param("query") CcdiFundGraphQueryDTO query);
|
||||
|
||||
List<CcdiFundGraphNodeVO> selectFundGraphSubjectsByExactKeyword(@Param("query") CcdiFundGraphQueryDTO query);
|
||||
|
||||
List<CcdiFundGraphNodeVO> selectFundGraphSubjectsByName(@Param("query") CcdiFundGraphQueryDTO query);
|
||||
|
||||
List<CcdiFundGraphEdgeVO> selectFundGraphEdges(@Param("query") CcdiFundGraphQueryDTO query);
|
||||
|
||||
List<CcdiFundGraphEdgeVO> selectFundGraphManualEdges(@Param("query") CcdiFundGraphQueryDTO query);
|
||||
|
||||
@@ -34,6 +34,7 @@ public class BankTagRuleConfigResolver {
|
||||
Map.entry("FOREX_BUY_AMT", Set.of("SINGLE_PURCHASE_AMOUNT")),
|
||||
Map.entry("FOREX_SELL_AMT", Set.of("SINGLE_SETTLEMENT_AMOUNT")),
|
||||
Map.entry("WITHDRAW_CNT", Set.of("WITHDRAW_CNT")),
|
||||
Map.entry("WITHDRAW_AMT", Set.of("WITHDRAW_AMT")),
|
||||
Map.entry("STOCK_TFR_LARGE", Set.of("STOCK_TFR_LARGE")),
|
||||
Map.entry("LARGE_STOCK_TRADING", Set.of("STOCK_TFR_LARGE")),
|
||||
Map.entry("MULTI_PARTY_GAMBLING_TRANSFER", Set.of("MULTI_PARTY_AMT_MIN", "MULTI_PARTY_AMT_MAX")),
|
||||
|
||||
@@ -285,7 +285,9 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
|
||||
case "WITHDRAW_CNT" -> analysisMapper.selectWithdrawCntObjects(
|
||||
projectId, toInteger(config.getThresholdValue("WITHDRAW_CNT"))
|
||||
);
|
||||
case "WITHDRAW_AMT" -> analysisMapper.selectWithdrawAmtObjects(projectId);
|
||||
case "WITHDRAW_AMT" -> analysisMapper.selectWithdrawAmtObjects(
|
||||
projectId, toBigDecimal(config.getThresholdValue("WITHDRAW_AMT"))
|
||||
);
|
||||
case "SALARY_QUICK_TRANSFER" -> analysisMapper.selectSalaryQuickTransferObjects(projectId);
|
||||
case "SALARY_UNUSED" -> analysisMapper.selectSalaryUnusedObjects(projectId);
|
||||
case "SUDDEN_ACCOUNT_CLOSURE" -> analysisMapper.selectSuddenAccountClosureObjects(projectId);
|
||||
|
||||
@@ -226,15 +226,18 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
|
||||
CcdiFileUploadRecord record = recordMapper.selectById(id);
|
||||
validateDeleteRecord(record);
|
||||
|
||||
DeleteFilesRequest request = new DeleteFilesRequest();
|
||||
request.setGroupId(record.getLsfxProjectId());
|
||||
request.setLogIds(new Integer[]{record.getLogId()});
|
||||
request.setUserId(toUploadUserId(operatorUserId));
|
||||
|
||||
DeleteFilesResponse response = lsfxClient.deleteFiles(request);
|
||||
if (response == null || Boolean.FALSE.equals(response.getSuccessResponse())) {
|
||||
throw new RuntimeException("流水分析平台删除文件失败");
|
||||
}
|
||||
/*
|
||||
* 按当前要求,项目管理-上传数据页面删除时先不调用流水分析平台删除接口。
|
||||
* DeleteFilesRequest request = new DeleteFilesRequest();
|
||||
* request.setGroupId(record.getLsfxProjectId());
|
||||
* request.setLogIds(new Integer[]{record.getLogId()});
|
||||
* request.setUserId(toUploadUserId(operatorUserId));
|
||||
*
|
||||
* DeleteFilesResponse response = lsfxClient.deleteFiles(request);
|
||||
* if (response == null || Boolean.FALSE.equals(response.getSuccessResponse())) {
|
||||
* throw new RuntimeException("流水分析平台删除文件失败");
|
||||
* }
|
||||
*/
|
||||
|
||||
bankStatementMapper.deleteByProjectIdAndBatchId(record.getProjectId(), record.getLogId());
|
||||
refreshProjectTargetCount(record.getProjectId());
|
||||
|
||||
@@ -48,7 +48,7 @@ public class CcdiFundGraphServiceImpl implements ICcdiFundGraphService {
|
||||
if (isBlank(query.getKeyword()) && isBlank(query.getObjectKey())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return fundGraphMapper.selectFundGraphSubjects(query);
|
||||
return selectSubjects(query);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -134,13 +134,30 @@ public class CcdiFundGraphServiceImpl implements ICcdiFundGraphService {
|
||||
if (isBlank(query.getObjectKey()) && isBlank(query.getKeyword())) {
|
||||
return null;
|
||||
}
|
||||
List<CcdiFundGraphNodeVO> subjects = fundGraphMapper.selectFundGraphSubjects(query);
|
||||
List<CcdiFundGraphNodeVO> subjects = selectSubjects(query);
|
||||
if (subjects == null || subjects.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return subjects.get(0);
|
||||
}
|
||||
|
||||
private List<CcdiFundGraphNodeVO> selectSubjects(CcdiFundGraphQueryDTO query) {
|
||||
if (query == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
if (!isBlank(query.getObjectKey())) {
|
||||
return fundGraphMapper.selectFundGraphSubjects(query);
|
||||
}
|
||||
if (isBlank(query.getKeyword())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<CcdiFundGraphNodeVO> exactSubjects = fundGraphMapper.selectFundGraphSubjectsByExactKeyword(query);
|
||||
if (exactSubjects != null && !exactSubjects.isEmpty()) {
|
||||
return exactSubjects;
|
||||
}
|
||||
return fundGraphMapper.selectFundGraphSubjectsByName(query);
|
||||
}
|
||||
|
||||
private List<CcdiFundGraphNodeVO> buildNodes(CcdiFundGraphNodeVO centerNode, List<CcdiFundGraphEdgeVO> edges) {
|
||||
Map<String, CcdiFundGraphNodeVO> nodeMap = new LinkedHashMap<>();
|
||||
Map<String, CcdiFundGraphNodeVO> subjectCache = new LinkedHashMap<>();
|
||||
|
||||
@@ -146,6 +146,60 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
)
|
||||
</sql>
|
||||
|
||||
<sql id="financialProductExclusionPredicate">
|
||||
not (
|
||||
(
|
||||
(
|
||||
IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '理财|理财产品|结构.*存款|结构性存款|理财.*托管|余额宝|朝朝宝|朝朝盈|现金宝|金添利|定存宝'
|
||||
or IFNULL(bs.USER_MEMO, '') REGEXP '理财|理财产品|结构.*存款|结构性存款|本金划出|本金返还|余额宝|朝朝宝|朝朝盈|现金宝|金添利|定存宝|整存整取|智能存款|通知存款'
|
||||
or IFNULL(bs.CASH_TYPE, '') REGEXP '受托理财|表内理财|购买理财|理财购买|理财扣款|理财申购|理财认购|结构性存款|存款产品|朝朝宝'
|
||||
or (
|
||||
IFNULL(bs.USER_MEMO, '') REGEXP '申购|认购|赎回'
|
||||
and IFNULL(bs.USER_MEMO, '') REGEXP '理财|产品|存款|本金|余额宝|朝朝宝|朝朝盈'
|
||||
)
|
||||
)
|
||||
and IFNULL(bs.USER_MEMO, '') NOT REGEXP '财务|经理|代理财税'
|
||||
and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') NOT LIKE '%代理财政%'
|
||||
)
|
||||
)
|
||||
</sql>
|
||||
|
||||
<sql id="thirdPartyWithdrawIncomePredicate">
|
||||
(
|
||||
(
|
||||
bs.BANK in ('ALIPAY', 'WECHAT')
|
||||
and (
|
||||
IFNULL(bs.CASH_TYPE, '') LIKE '%提现%'
|
||||
or IFNULL(bs.USER_MEMO, '') LIKE '%提现%'
|
||||
or IFNULL(bs.USER_MEMO, '') LIKE '%转出到%银行%'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%提现%'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%转出到%银行%'
|
||||
)
|
||||
)
|
||||
or (
|
||||
(
|
||||
bs.BANK is null
|
||||
or bs.BANK = ''
|
||||
or bs.BANK not in ('ALIPAY', 'WECHAT')
|
||||
)
|
||||
and (
|
||||
IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '支付宝|Alipay|财付通|Tenpay|微信|wechat|WeChat|微信零钱'
|
||||
or IFNULL(bs.USER_MEMO, '') REGEXP '支付宝|Alipay|财付通|Tenpay|微信|wechat|WeChat|微信零钱'
|
||||
or IFNULL(bs.CASH_TYPE, '') REGEXP '支付宝|Alipay|财付通|Tenpay|微信|wechat|WeChat|微信零钱'
|
||||
)
|
||||
and (
|
||||
IFNULL(bs.CASH_TYPE, '') LIKE '%提现%'
|
||||
or IFNULL(bs.USER_MEMO, '') LIKE '%提现%'
|
||||
or IFNULL(bs.USER_MEMO, '') LIKE '%转出到%银行%'
|
||||
or IFNULL(bs.USER_MEMO, '') LIKE '%提现到账%'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%提现%'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%转出到%银行%'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%提现到账%'
|
||||
)
|
||||
)
|
||||
)
|
||||
</sql>
|
||||
|
||||
<sql id="abnormalCustomerTransactionSubjectSql">
|
||||
select
|
||||
staff.id_card as subjectCertNo,
|
||||
@@ -200,6 +254,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
IFNULL(bs.USER_MEMO, '') REGEXP '(购|买).*房|(购|买).*车|车款|房款|首付|(房|车).*贷'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '汽车销售|汽车金融|4S店|汽贸|车行|房地产|置业|置地|地产|房产|不动产|链家|贝壳|我爱我家|房管局'
|
||||
)
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
and (
|
||||
exists (
|
||||
select 1
|
||||
@@ -231,6 +286,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
IFNULL(bs.USER_MEMO, '') REGEXP '税务|缴税|税款'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '税务|税务局|国库|国家金库|财政'
|
||||
)
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
and (
|
||||
exists (
|
||||
select 1
|
||||
@@ -268,6 +324,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and IFNULL(bs.LE_ACCOUNT_NAME, '') <> IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
|
||||
and relation.person_id is null
|
||||
and <include refid="salaryExclusionPredicate"/>
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
</select>
|
||||
|
||||
<select id="selectCumulativeIncomeObjects" resultMap="BankTagObjectHitResultMap">
|
||||
@@ -295,6 +352,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
and IFNULL(bs.LE_ACCOUNT_NAME, '') <> IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
|
||||
and relation.person_id is null
|
||||
and <include refid="salaryExclusionPredicate"/>
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
group by staff.id_card, bs.CUSTOMER_ACCOUNT_NAME
|
||||
having SUM(IFNULL(bs.AMOUNT_CR, 0)) > #{threshold}
|
||||
) t
|
||||
@@ -316,6 +374,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.LE_ACCOUNT_NAME, '') <> IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
and STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d') >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
|
||||
group by staff.id_card
|
||||
having SUM(IFNULL(bs.AMOUNT_DR, 0) + IFNULL(bs.AMOUNT_CR, 0)) > #{threshold}
|
||||
@@ -335,19 +394,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.AMOUNT_CR, 0) > #{threshold}
|
||||
and <include refid="cashDepositPredicate"/>
|
||||
and (
|
||||
exists (
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
and exists (
|
||||
select 1
|
||||
from ccdi_base_staff staff
|
||||
where staff.id_card = bs.cret_no
|
||||
)
|
||||
or exists (
|
||||
select 1
|
||||
from ccdi_staff_fmy_relation relation
|
||||
where relation.relation_cert_no = bs.cret_no
|
||||
and relation.status = 1
|
||||
)
|
||||
)
|
||||
</select>
|
||||
|
||||
<select id="selectFrequentCashDepositObjects" resultMap="BankTagObjectHitResultMap">
|
||||
@@ -373,6 +425,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.AMOUNT_CR, 0) > #{amountThreshold}
|
||||
and <include refid="cashDepositPredicate"/>
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
) source
|
||||
group by source.object_key, source.cash_date
|
||||
having COUNT(1) > #{frequencyThreshold}
|
||||
@@ -396,8 +449,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
or IFNULL(bs.USER_MEMO, '') REGEXP '转帐|转账|汇入|转存|红包|汇款|网转|转入'
|
||||
or IFNULL(bs.CASH_TYPE, '') REGEXP '转帐|转账|汇入|转存|红包|汇款|网转|转入'
|
||||
)
|
||||
and IFNULL(bs.USER_MEMO, '') NOT LIKE '%款%'
|
||||
and IFNULL(bs.LE_ACCOUNT_NAME, '') <> IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
and (
|
||||
exists (
|
||||
select 1
|
||||
@@ -644,8 +697,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.AMOUNT_DR, 0) > 0
|
||||
and (
|
||||
IFNULL(bs.USER_MEMO, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌|球|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|捕鱼|电子游艺|投注'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌|球|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|捕鱼|电子游艺|投注'
|
||||
IFNULL(bs.USER_MEMO, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|捕鱼|电子游艺|VIP666|USDT下注'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|捕鱼|电子游艺|VIP666|USDT下注'
|
||||
)
|
||||
</select>
|
||||
|
||||
@@ -1205,6 +1258,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
or IFNULL(bs.USER_MEMO, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证'
|
||||
or IFNULL(bs.CASH_TYPE, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证'
|
||||
)
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
</select>
|
||||
|
||||
<select id="selectWithdrawCntObjects" resultMap="BankTagObjectHitResultMap">
|
||||
@@ -1224,11 +1278,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
from ccdi_bank_statement bs
|
||||
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.AMOUNT_CR, 0) >= 0
|
||||
and (
|
||||
IFNULL(bs.USER_MEMO, '') REGEXP '财付通|微信零钱|微信|wechat|WeChat|Tenpay|支付宝|Alipay|提现'
|
||||
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '财付通|微信零钱|微信|wechat|WeChat|Tenpay|支付宝|Alipay|提现'
|
||||
)
|
||||
and IFNULL(bs.AMOUNT_CR, 0) > 0
|
||||
and <include refid="thirdPartyWithdrawIncomePredicate"/>
|
||||
group by staff.id_card, LEFT(TRIM(bs.TRX_DATE), 10)
|
||||
having COUNT(1) > #{frequencyThreshold}
|
||||
) t
|
||||
@@ -1237,10 +1288,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<select id="selectWithdrawAmtObjects" resultMap="BankTagObjectHitResultMap">
|
||||
select
|
||||
'STAFF_ID_CARD' AS objectType,
|
||||
'' AS objectKey,
|
||||
'占位SQL,待补充真实规则' AS reasonDetail
|
||||
t.objectKey AS objectKey,
|
||||
CONCAT(
|
||||
'单日微信/支付宝提现到账金额 ', CAST(t.withdrawAmount AS CHAR),
|
||||
' 元,超过阈值 ', CAST(#{amountThreshold} AS CHAR),
|
||||
' 元,交易日:', t.transDate
|
||||
) AS reasonDetail
|
||||
from (
|
||||
select
|
||||
staff.id_card AS objectKey,
|
||||
LEFT(TRIM(bs.TRX_DATE), 10) AS transDate,
|
||||
ROUND(SUM(IFNULL(bs.AMOUNT_CR, 0)), 2) AS withdrawAmount
|
||||
from ccdi_bank_statement bs
|
||||
where 1 = 0
|
||||
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.AMOUNT_CR, 0) > 0
|
||||
and <include refid="thirdPartyWithdrawIncomePredicate"/>
|
||||
group by staff.id_card, LEFT(TRIM(bs.TRX_DATE), 10)
|
||||
having SUM(IFNULL(bs.AMOUNT_CR, 0)) > #{amountThreshold}
|
||||
) t
|
||||
</select>
|
||||
|
||||
<select id="selectSalaryQuickTransferObjects" resultMap="BankTagObjectHitResultMap">
|
||||
@@ -1472,10 +1538,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
where bs.project_id = #{projectId}
|
||||
and IFNULL(bs.AMOUNT_DR, 0) > #{threshold}
|
||||
and (
|
||||
IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|理财|资金存管|第三方存管'
|
||||
or IFNULL(bs.USER_MEMO, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|理财|资金存管|第三方存管'
|
||||
or IFNULL(bs.CASH_TYPE, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|理财|资金存管|第三方存管'
|
||||
IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管'
|
||||
or IFNULL(bs.USER_MEMO, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证'
|
||||
or IFNULL(bs.CASH_TYPE, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证'
|
||||
)
|
||||
and <include refid="financialProductExclusionPredicate"/>
|
||||
</select>
|
||||
|
||||
<select id="selectProxyAccountOperationObjects" resultMap="BankTagObjectHitResultMap">
|
||||
|
||||
@@ -116,17 +116,79 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
INNER JOIN lx_fund_flow_subject_node to_subject
|
||||
ON CONCAT('idno_node/', to_subject.object_key) = to_own.from_key
|
||||
WHERE 1 = 1
|
||||
<if test="query.objectKey != null and query.objectKey != ''">
|
||||
AND (
|
||||
from_subject.object_key = (#{query.objectKey} COLLATE utf8mb4_general_ci)
|
||||
OR to_subject.object_key = (#{query.objectKey} COLLATE utf8mb4_general_ci)
|
||||
)
|
||||
</if>
|
||||
<include refid="detailFilter"/>
|
||||
</sql>
|
||||
|
||||
<select id="selectFundGraphSubjects" resultMap="FundGraphNodeResultMap">
|
||||
<sql id="subjectJoinRowsByCenter">
|
||||
SELECT
|
||||
d.object_key AS detailObjectKey,
|
||||
d.bank_statement_id AS bankStatementId,
|
||||
d.trx_date AS trxDate,
|
||||
d.le_account_no AS leAccountNo,
|
||||
d.le_account_name AS leAccountName,
|
||||
d.customer_account_name AS customerAccountName,
|
||||
d.customer_account_no AS customerAccountNo,
|
||||
d.cash_type AS cashType,
|
||||
d.user_memo AS userMemo,
|
||||
d.amount,
|
||||
d.flag AS direction,
|
||||
d.family_relation_type AS familyRelationType,
|
||||
center_subject.object_key AS fromObjectKey,
|
||||
to_subject.object_key AS toObjectKey,
|
||||
CONCAT('idno_node/', center_subject.object_key) AS fromKey,
|
||||
CONCAT('idno_node/', to_subject.object_key) AS toKey,
|
||||
center_subject.name AS fromName,
|
||||
to_subject.name AS toName,
|
||||
center_subject.idnocfno AS fromIdNo,
|
||||
to_subject.idnocfno AS toIdNo
|
||||
FROM lx_fund_flow_subject_node center_subject
|
||||
INNER JOIN lx_fund_flow_own_account_edge from_own
|
||||
ON from_own.from_key = CONCAT('idno_node/', center_subject.object_key)
|
||||
INNER JOIN lx_fund_flow_detail_edge d
|
||||
ON d.from_key = from_own.to_key
|
||||
INNER JOIN lx_fund_flow_own_account_edge to_own
|
||||
ON to_own.to_key = d.to_key
|
||||
INNER JOIN lx_fund_flow_subject_node to_subject
|
||||
ON to_subject.object_key = SUBSTRING(to_own.from_key, 11)
|
||||
WHERE center_subject.object_key = (#{query.objectKey} COLLATE utf8mb4_general_ci)
|
||||
<include refid="detailFilter"/>
|
||||
UNION ALL
|
||||
SELECT
|
||||
d.object_key AS detailObjectKey,
|
||||
d.bank_statement_id AS bankStatementId,
|
||||
d.trx_date AS trxDate,
|
||||
d.le_account_no AS leAccountNo,
|
||||
d.le_account_name AS leAccountName,
|
||||
d.customer_account_name AS customerAccountName,
|
||||
d.customer_account_no AS customerAccountNo,
|
||||
d.cash_type AS cashType,
|
||||
d.user_memo AS userMemo,
|
||||
d.amount,
|
||||
d.flag AS direction,
|
||||
d.family_relation_type AS familyRelationType,
|
||||
from_subject.object_key AS fromObjectKey,
|
||||
center_subject.object_key AS toObjectKey,
|
||||
CONCAT('idno_node/', from_subject.object_key) AS fromKey,
|
||||
CONCAT('idno_node/', center_subject.object_key) AS toKey,
|
||||
from_subject.name AS fromName,
|
||||
center_subject.name AS toName,
|
||||
from_subject.idnocfno AS fromIdNo,
|
||||
center_subject.idnocfno AS toIdNo
|
||||
FROM lx_fund_flow_subject_node center_subject
|
||||
INNER JOIN lx_fund_flow_own_account_edge to_own
|
||||
ON to_own.from_key = CONCAT('idno_node/', center_subject.object_key)
|
||||
INNER JOIN lx_fund_flow_detail_edge d
|
||||
ON d.to_key = to_own.to_key
|
||||
INNER JOIN lx_fund_flow_own_account_edge from_own
|
||||
ON from_own.to_key = d.from_key
|
||||
INNER JOIN lx_fund_flow_subject_node from_subject
|
||||
ON from_subject.object_key = SUBSTRING(from_own.from_key, 11)
|
||||
WHERE center_subject.object_key = (#{query.objectKey} COLLATE utf8mb4_general_ci)
|
||||
AND from_subject.object_key != center_subject.object_key
|
||||
<include refid="detailFilter"/>
|
||||
</sql>
|
||||
|
||||
<sql id="fundGraphSubjectColumns">
|
||||
n.object_key AS objectKey,
|
||||
CONCAT('idno_node/', n.object_key) AS nodeKey,
|
||||
n.name AS nodeName,
|
||||
@@ -165,28 +227,57 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
0 AS depth,
|
||||
0 AS totalAmount,
|
||||
0 AS transactionCount
|
||||
</sql>
|
||||
|
||||
<select id="selectFundGraphSubjects" resultMap="FundGraphNodeResultMap">
|
||||
SELECT
|
||||
<include refid="fundGraphSubjectColumns"/>
|
||||
FROM lx_fund_flow_subject_node n
|
||||
WHERE 1 = 1
|
||||
<if test="query.objectKey != null and query.objectKey != ''">
|
||||
AND n.object_key = (#{query.objectKey} COLLATE utf8mb4_general_ci)
|
||||
</if>
|
||||
<if test="query.objectKey == null or query.objectKey == ''">
|
||||
<if test="query.keyword != null and query.keyword != ''">
|
||||
WHERE n.object_key = (#{query.objectKey} COLLATE utf8mb4_general_ci)
|
||||
</select>
|
||||
|
||||
<select id="selectFundGraphSubjectsByExactKeyword" resultMap="FundGraphNodeResultMap">
|
||||
SELECT exact_rows.*
|
||||
FROM (
|
||||
SELECT
|
||||
<include refid="fundGraphSubjectColumns"/>,
|
||||
0 AS matchOrder
|
||||
FROM lx_fund_flow_subject_node n
|
||||
WHERE n.idnocfno = (TRIM(#{query.keyword}) COLLATE utf8mb4_general_ci)
|
||||
UNION ALL
|
||||
SELECT
|
||||
<include refid="fundGraphSubjectColumns"/>,
|
||||
1 AS matchOrder
|
||||
FROM lx_fund_flow_subject_node n
|
||||
WHERE n.object_key = (TRIM(#{query.keyword}) COLLATE utf8mb4_general_ci)
|
||||
AND (
|
||||
n.idnocfno = (TRIM(#{query.keyword}) COLLATE utf8mb4_general_ci)
|
||||
OR n.name LIKE (CONCAT('%', TRIM(#{query.keyword}), '%') COLLATE utf8mb4_general_ci)
|
||||
OR n.object_key = (TRIM(#{query.keyword}) COLLATE utf8mb4_general_ci)
|
||||
n.idnocfno IS NULL
|
||||
OR n.idnocfno != (TRIM(#{query.keyword}) COLLATE utf8mb4_general_ci)
|
||||
)
|
||||
</if>
|
||||
</if>
|
||||
) exact_rows
|
||||
ORDER BY exact_rows.matchOrder, exact_rows.nodeName
|
||||
LIMIT
|
||||
<choose>
|
||||
<when test="query.limit != null and query.limit > 0">
|
||||
#{query.limit}
|
||||
</when>
|
||||
<otherwise>
|
||||
20
|
||||
</otherwise>
|
||||
</choose>
|
||||
</select>
|
||||
|
||||
<select id="selectFundGraphSubjectsByName" resultMap="FundGraphNodeResultMap">
|
||||
SELECT
|
||||
<include refid="fundGraphSubjectColumns"/>
|
||||
FROM lx_fund_flow_subject_node n
|
||||
WHERE n.name LIKE (CONCAT('%', TRIM(#{query.keyword}), '%') COLLATE utf8mb4_general_ci)
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN n.idnocfno = (TRIM(IFNULL(#{query.keyword}, '')) COLLATE utf8mb4_general_ci) THEN 0
|
||||
WHEN n.object_key = (TRIM(IFNULL(#{query.keyword}, '')) COLLATE utf8mb4_general_ci) THEN 1
|
||||
WHEN n.staff_id IS NOT NULL AND TRIM(n.staff_id) != '' THEN 2
|
||||
WHEN UPPER(IFNULL(n.source_type, '')) LIKE '%EMPLOYEE%' THEN 2
|
||||
WHEN n.source_type LIKE '%员工%' THEN 2
|
||||
ELSE 3
|
||||
WHEN n.staff_id IS NOT NULL AND TRIM(n.staff_id) != '' THEN 0
|
||||
WHEN UPPER(IFNULL(n.source_type, '')) LIKE '%EMPLOYEE%' THEN 0
|
||||
WHEN n.source_type LIKE '%员工%' THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
n.name
|
||||
LIMIT
|
||||
@@ -247,7 +338,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
ELSE 0
|
||||
END AS canTrace
|
||||
FROM (
|
||||
<include refid="subjectJoinRows"/>
|
||||
<include refid="subjectJoinRowsByCenter"/>
|
||||
) graph_rows
|
||||
WHERE 1 = 1
|
||||
GROUP BY
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# 结果总览弹窗资金流向逐笔流水展示前端实施计划
|
||||
|
||||
## 需求范围
|
||||
|
||||
- 修改结果总览“查看详情”弹窗中的“资金流向”页签。
|
||||
- 去掉资金边详情中的“弹窗速览”提示文案。
|
||||
- 在弹窗资金边详情中展示逐笔流水明细,并保持分页加载。
|
||||
- 不修改专项排查页的图谱入口和完整图谱展示逻辑。
|
||||
|
||||
## 实施方案
|
||||
|
||||
1. 调整 `ProjectAnalysisFundFlowTab.vue` 中传给 `FundGraphSection` 的参数,开启资金边逐笔流水表格。
|
||||
2. 保持 `FundGraphSection` 现有边明细接口调用逻辑不变,继续使用分页查询。
|
||||
3. 在弹窗包装组件内改为上方图谱、下方逐笔流水布局,收敛表格和分页样式,避免逐笔流水表撑高或挤压图谱画布。
|
||||
4. 不新增接口、不修改后端、不改变专项排查页完整下钻能力。
|
||||
|
||||
## 验证计划
|
||||
|
||||
- 前端构建前按项目规则执行 `nvm use` 并确认 Node 版本。
|
||||
- 执行前端构建或聚焦测试,确认组件编译通过。
|
||||
- 使用真实页面打开结果总览“查看详情”弹窗,切换到“资金流向”,点击资金边确认下方显示逐笔流水和分页。
|
||||
@@ -0,0 +1,36 @@
|
||||
# 结果总览弹窗资金流向可用性优化前端实施计划
|
||||
|
||||
## 目标
|
||||
|
||||
- 优化结果总览“查看详情”弹窗内“资金流向”页签的图谱展示空间。
|
||||
- 降低多条资金边金额标签、节点名称在小画布中重叠的问题。
|
||||
- 在查看单个对手方资金边明细后,通过点击图谱画布空白区域恢复全量图谱状态。
|
||||
|
||||
## 实施范围
|
||||
|
||||
- 仅调整结果总览“查看详情”弹窗中的资金流向图谱。
|
||||
- 不调整专项排查页资金图谱的默认尺寸和业务逻辑。
|
||||
- 不修改后端接口、数据库和资金流水分页接口。
|
||||
|
||||
## 实施方案
|
||||
|
||||
1. 在 `ProjectAnalysisFundFlowTab` 中扩大资金流向工作区尺寸:
|
||||
- 提高弹窗内图谱卡片高度。
|
||||
- 改为上方图谱、下方逐笔流水布局,给图谱保留更大画布空间。
|
||||
- 下方逐笔流水表格保持固定高度和分页展示,避免撑高弹窗。
|
||||
|
||||
2. 在 `FundGraphSection` 中增加弹窗可配置能力:
|
||||
- 增加边标签紧凑展示开关,金额使用“万/亿”等短格式展示。
|
||||
- 支持隐藏资金边汇总卡片,只保留逐笔流水明细。
|
||||
- 点击图谱画布空白区域时清空选中节点、选中边和逐笔流水明细,并重新渲染图谱。
|
||||
|
||||
3. 优化选中状态表达:
|
||||
- 图谱中当前边和两端节点保持高亮,其他边降低透明度。
|
||||
- 点空白区域后恢复初始全量图谱状态,不额外增加按钮。
|
||||
|
||||
## 验证计划
|
||||
|
||||
- 执行前端构建,确认无编译错误。
|
||||
- 在真实页面进入结果总览“查看详情”弹窗,切换到“资金流向”。
|
||||
- 选择包含多笔交易金额标签的资金边,验证节点名称和金额标签不再严重叠加,逐笔流水显示在图谱下方。
|
||||
- 点击图谱画布空白区域,验证逐笔流水清空,图谱恢复全量状态。
|
||||
@@ -0,0 +1,20 @@
|
||||
# 图谱展示恢复外链版本实施记录
|
||||
|
||||
## 基本信息
|
||||
|
||||
- 实施日期:2026-06-01
|
||||
- 实施分支:dev
|
||||
- 恢复前版本保存分支:codex/save-dev-current-graph
|
||||
- 影响范围:项目详情专项排查页图谱展示区域
|
||||
|
||||
## 修改内容
|
||||
|
||||
- 将专项排查页 `SpecialCheck.vue` 的图谱展示组件从内置资金图谱 `FundGraphSection` 恢复为外链图谱组件 `GraphAtlasSection`。
|
||||
- 保留员工家庭资产负债数据为空时的空状态展示,同时在空状态下继续展示外链图谱查询入口。
|
||||
- 保持扩展查询区域仍位于专项排查主体内容之后。
|
||||
|
||||
## 验证情况
|
||||
|
||||
- 已执行 `git diff --check`,未发现空白字符问题。
|
||||
- 已通过 `nvm use` 切换到 Node `v14.21.3`,执行 `npm run build:prod` 通过。
|
||||
- 构建过程存在前端项目既有资源体积告警,不影响本次恢复外链图谱展示的打包结果。
|
||||
@@ -0,0 +1,79 @@
|
||||
# 资金流图谱接口超时优化实施记录
|
||||
|
||||
## 背景
|
||||
|
||||
- 生产反馈:资金流图谱页面请求 `/ccdi/project/fund-graph/graph` 出现接口超时。
|
||||
- 截图中的请求参数为 `keyword=330781199401056317`,属于身份证号精确查询场景。
|
||||
- 生产数据量约 61 万主体时,原主体定位 SQL 将身份证精确匹配、姓名模糊匹配、`object_key` 精确匹配混在同一个 `OR` 条件内,并叠加 `ORDER BY CASE`,容易导致优化器选择低效执行计划。
|
||||
|
||||
## 修改内容
|
||||
|
||||
1. 拆分主体定位查询:
|
||||
- 文件:`ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiFundGraphMapper.java`
|
||||
- 文件:`ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFundGraphMapper.xml`
|
||||
- 新增 `selectFundGraphSubjectsByExactKeyword`,只查询 `idnocfno = keyword` 与 `object_key = keyword`。
|
||||
- 新增 `selectFundGraphSubjectsByName`,仅在精确查询无结果后再执行 `name LIKE '%keyword%'`。
|
||||
- `selectFundGraphSubjects` 保留为 `object_key` 主键查询,不再执行额外排序。
|
||||
|
||||
2. 调整 Service 调用顺序:
|
||||
- 文件:`ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFundGraphServiceImpl.java`
|
||||
- `/search` 与 `/graph` 的中心节点解析统一走 `selectSubjects`。
|
||||
- 有 `objectKey` 时直接主键查询。
|
||||
- 无 `objectKey` 且有 `keyword` 时,先精确查询;精确命中后直接返回,不再执行姓名模糊查询。
|
||||
|
||||
3. 优化图谱边查询入口:
|
||||
- 文件:`ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFundGraphMapper.xml`
|
||||
- 新增 `subjectJoinRowsByCenter`,将“中心主体作为起点”和“中心主体作为终点”拆为两段 `UNION ALL`。
|
||||
- `selectFundGraphEdges` 改为引用 `subjectJoinRowsByCenter`,避免在主体表两侧使用 `from_subject.object_key OR to_subject.object_key` 作为过滤入口。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 资金流图谱主体搜索:`GET /ccdi/project/fund-graph/search`
|
||||
- 资金流图谱查询:`GET /ccdi/project/fund-graph/graph`
|
||||
- 边明细查询 `GET /ccdi/project/fund-graph/edge-detail` 仍使用原明细 SQL 片段,未改变入参和返回结构。
|
||||
- 本次未新增数据库字段或索引脚本;现有 DDL 中已包含 `lx_fund_flow_subject_node.idnocfno`、`name` 和 `object_key` 相关索引。
|
||||
|
||||
## 验证情况
|
||||
|
||||
- 已通过静态断言确认:
|
||||
- Mapper 与 XML 已新增精确查询和姓名查询方法。
|
||||
- XML 中已移除 `OR n.name LIKE` 的精确/模糊混用形态。
|
||||
- `selectFundGraphEdges` 已引用 `subjectJoinRowsByCenter` 且包含 `UNION ALL`。
|
||||
- 已通过 XML 格式检查:
|
||||
- `xmllint --noout ccdi-project/src/main/resources/mapper/ccdi/project/CcdiFundGraphMapper.xml`
|
||||
- 已通过 MyBatis XML 解析和 SQL 渲染临时检查:
|
||||
- `selectFundGraphSubjectsByExactKeyword`
|
||||
- `selectFundGraphSubjectsByName`
|
||||
- `selectFundGraphEdges`
|
||||
- 已通过源码编译:
|
||||
- `mvn -pl ccdi-project -am -DskipTests compile`
|
||||
|
||||
## 验证阻断说明
|
||||
|
||||
- 直接执行 `mvn -pl ccdi-project -DskipTests compile` 会使用本地仓库中的旧依赖模块,出现既有签名不一致错误;使用 `-am` 联编依赖模块后通过。
|
||||
- 直接执行 `mvn -pl ccdi-project -Dtest=CcdiFundGraphTimeoutOptimizationTest test` 时,测试编译阶段被既有无关测试源码阻断,未进入本次临时测试断言:
|
||||
- `CcdiBankStatementTest` 仍引用 `BankStatementItem#setCustomerCertNo`、`setCustomerSocialCreditCode`。
|
||||
- `CcdiFileUploadServiceImplTest` 仍按旧的 `LsfxAnalysisClient.uploadFile(Integer, Object, String)` 签名编写。
|
||||
|
||||
## 生产核对建议
|
||||
|
||||
- 发布后用生产超时样例身份证号重新请求 `/ccdi/project/fund-graph/graph`,核对接口耗时与返回图谱中心节点。
|
||||
- 如生产库是历史库,先确认以下索引存在:
|
||||
|
||||
```sql
|
||||
SHOW INDEX FROM lx_fund_flow_subject_node WHERE Key_name IN (
|
||||
'PRIMARY',
|
||||
'idx_lx_fund_flow_subject_idnocfno',
|
||||
'idx_lx_fund_flow_subject_name'
|
||||
);
|
||||
|
||||
SHOW INDEX FROM lx_fund_flow_own_account_edge WHERE Key_name IN (
|
||||
'idx_lx_fund_flow_own_from_key',
|
||||
'idx_lx_fund_flow_own_to_key'
|
||||
);
|
||||
|
||||
SHOW INDEX FROM lx_fund_flow_detail_edge WHERE Key_name IN (
|
||||
'idx_lx_fund_flow_detail_from_date',
|
||||
'idx_lx_fund_flow_detail_to_date'
|
||||
);
|
||||
```
|
||||
@@ -0,0 +1,38 @@
|
||||
# 银行流水打标理财类流水剔除实施记录
|
||||
|
||||
## 修改背景
|
||||
|
||||
- 员工纪检场景下,单纯理财申购、理财购买、结构性存款、理财赎回等流水属于资产形态转换,不应仅因金额较大命中大额交易或炒股模型。
|
||||
- 桌面本地知识库中的流水底层打标规则将“理财支出/理财收回”作为独立标签口径,银证类逻辑更聚焦“证券/期货 + 银证转账/银转证/证转银”等特征。
|
||||
|
||||
## 修改内容
|
||||
|
||||
- 在 `CcdiBankTagAnalysisMapper.xml` 新增 `financialProductExclusionPredicate` SQL 片段,按当前流水字段识别理财类流水并排除。
|
||||
- 大额交易模型接入理财排除:
|
||||
- 房车消费支出交易
|
||||
- 税务支出交易
|
||||
- 大额单笔收入
|
||||
- 累计收入超限
|
||||
- 年流水交易额超限
|
||||
- 大额存现交易
|
||||
- 短时间多次存现
|
||||
- 大额转账交易
|
||||
- 炒股相关模型接入理财排除:
|
||||
- 可疑银证大额转账
|
||||
- 大额炒股
|
||||
- 从“大额炒股”关键词中移除 `理财`,并补充银证转账、银证、证转银、银转证等更贴近炒股转账的关键词。
|
||||
- 大额转账交易删除 `USER_MEMO NOT LIKE '%款%'` 排除条件。业务口径为“大额转账单笔超过设置限额”,不应因摘要包含“借款、还款、工程款、货款、服务款”等“款”字统一剔除,避免员工纪检场景漏报。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 仅影响银行流水打标 SQL 命中口径,不修改前端、接口结构、参数编码和数据库结构。
|
||||
- 理财类流水将不再因金额较大命中大额交易模型,也不会因包含“理财”关键词命中炒股模型。
|
||||
- 单独出现“申购/认购/赎回”不会直接被排除,必须同时伴随理财、产品、存款、本金、余额宝、朝朝宝等理财特征,避免误排证券申购或证转银场景。
|
||||
- 大额转账交易会重新纳入摘要包含“款”的大额转账流水,仍保留同名账户排除与理财类流水排除。
|
||||
|
||||
## 验证情况
|
||||
|
||||
- XML 解析校验通过:`CcdiBankTagAnalysisMapper.xml` 可正常解析,当前包含 35 个 `select` 与 9 个 `sql` 片段。
|
||||
- 后端编译验证通过:`mvn -pl ccdi-project -am compile -DskipTests` 执行成功。
|
||||
- 编译过程中 Maven 提示 `ccdi-info-collection` 存在既有 `ccdi-lsfx` 重复依赖声明警告,本次未修改该模块。
|
||||
- 本次不启动前端页面测试,修改范围为后端 Mapper SQL。
|
||||
@@ -0,0 +1,42 @@
|
||||
# 微信支付宝提现打标规则实施记录
|
||||
|
||||
## 修改背景
|
||||
|
||||
- 当前 `WITHDRAW_CNT` 使用 `AMOUNT_CR >= 0` 与微信、支付宝关键词识别频繁提现,容易把消费、转账、充值、退款等第三方支付流水误计为提现。
|
||||
- `WITHDRAW_AMT` 规则元数据和参数已存在,但 Mapper SQL 仍为占位实现,实际不会命中。
|
||||
- 本次参考桌面本地知识库中支付宝、微信提现识别口径:优先使用 `BANK in ('ALIPAY','WECHAT')` 与提现特征;当 `BANK` 未识别到微信或支付宝时,再使用平台关键词与提现特征组合兜底。
|
||||
|
||||
## 修改内容
|
||||
|
||||
- 在 `CcdiBankTagAnalysisMapper.xml` 新增 `thirdPartyWithdrawIncomePredicate`,统一识别微信、支付宝提现到账流水。
|
||||
- 调整 `WITHDRAW_CNT`:
|
||||
- 金额方向改为 `AMOUNT_CR > 0`,表示提现到账收入。
|
||||
- 不再仅凭微信、支付宝、财付通等平台关键词计数。
|
||||
- 按员工和交易日统计提现到账次数,超过 `WITHDRAW_CNT` 阈值命中。
|
||||
- 补齐 `WITHDRAW_AMT`:
|
||||
- 按员工和交易日汇总提现到账收入金额。
|
||||
- 超过 `WITHDRAW_AMT` 阈值命中。
|
||||
- Java 调用链补充 `WITHDRAW_AMT` 参数传递:
|
||||
- Mapper 方法增加 `amountThreshold` 参数。
|
||||
- Service 执行规则时传入 `WITHDRAW_AMT` 参数。
|
||||
- 参数解析器增加 `WITHDRAW_AMT -> WITHDRAW_AMT` 映射。
|
||||
|
||||
## 性能考虑
|
||||
|
||||
- SQL 保留 `project_id`、`AMOUNT_CR > 0`、员工身份证关联等基础过滤,先缩小参与匹配的数据集。
|
||||
- `BANK in ('ALIPAY','WECHAT')` 使用精确判断优先命中。
|
||||
- 只有 `BANK` 未识别为 `ALIPAY/WECHAT` 时,才走支付宝、微信、财付通等关键词兜底,降低误报和无谓匹配范围。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 仅影响异常行为模型下:
|
||||
- `WITHDRAW_CNT` 微信支付宝频繁提现
|
||||
- `WITHDRAW_AMT` 微信支付宝提现超额
|
||||
- 不修改前端、数据库结构和菜单权限。
|
||||
|
||||
## 验证情况
|
||||
|
||||
- XML 解析校验通过:`CcdiBankTagAnalysisMapper.xml` 可正常解析,当前包含 35 个 `select` 与 10 个 `sql` 片段。
|
||||
- 后端编译验证通过:`mvn -pl ccdi-project -am compile -DskipTests` 执行成功。
|
||||
- 文本核对通过:`WITHDRAW_CNT` 已不再使用 `AMOUNT_CR >= 0`,`WITHDRAW_AMT` 已不再使用占位 SQL。
|
||||
- 编译过程中 Maven 提示 `ccdi-info-collection` 存在既有 `ccdi-lsfx` 重复依赖声明警告,本次未修改该模块。
|
||||
@@ -0,0 +1,32 @@
|
||||
# 大额存现与赌博敏感关键词调整实施记录
|
||||
|
||||
## 修改时间
|
||||
|
||||
2026-06-04
|
||||
|
||||
## 修改背景
|
||||
|
||||
员工纪检流水打标模型中,大额存现交易需要按员工本人名下流水统计,不再纳入家庭成员名下流水。疑似赌博敏感交易需要覆盖交易摘要或交易对手中的“游戏、抖币、体彩、福彩”等关注字眼,同时删除单字“球”以减少正常球类消费误报。
|
||||
|
||||
## 修改内容
|
||||
|
||||
1. 调整 `selectLargeCashDepositStatements`:
|
||||
- 保留员工本人证件号匹配 `ccdi_base_staff.id_card = ccdi_bank_statement.cret_no`。
|
||||
- 移除家庭成员证件号匹配 `ccdi_staff_fmy_relation.relation_cert_no = ccdi_bank_statement.cret_no`。
|
||||
|
||||
2. 调整 `selectGamblingSensitiveKeywordStatements`:
|
||||
- 删除宽泛单字关键词 `球`。
|
||||
- 保留并补充博彩及敏感娱乐关键词:`游戏`、`抖币`、`体彩`、`福彩`、`彩票`、`赌博`、`赌球`、`下注`、`投注`、`球赛投注`、`外围`、`博彩`、`六合`、`时时彩`、`赛车`、`赌场`、`筹码`、`盘口`、`返水`、`洗码`、`庄家`、`闲家`、`百家乐`、`斗牛`、`炸金花`、`牌九`、`麻将`、`捕鱼`、`电子游艺`、`VIP666`、`USDT下注`。
|
||||
- 命中字段仍为交易摘要 `USER_MEMO` 和交易对手 `CUSTOMER_ACCOUNT_NAME`。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 大额存现交易命中范围收窄为员工本人名下流水。
|
||||
- 疑似赌博敏感交易继续覆盖游戏、抖币等关注字眼,同时减少单字“球”造成的正常球类消费误报。
|
||||
- 不影响短时间多次存现规则;该规则此前已经仅统计员工本人名下流水。
|
||||
- 不新增表结构和参数配置。
|
||||
|
||||
## 验证情况
|
||||
|
||||
- 已执行 MyBatis XML 解析检查。
|
||||
- 已执行 `mvn -pl ccdi-project -am compile -DskipTests` 编译验证。
|
||||
@@ -0,0 +1,30 @@
|
||||
# 结果总览弹窗资金流向逐笔流水展示实施记录
|
||||
|
||||
## 修改内容
|
||||
|
||||
- 去除结果总览“查看详情”弹窗资金边详情中的“弹窗速览”提示。
|
||||
- 结果总览弹窗“资金流向”页签启用逐笔流水表格展示。
|
||||
- 保持逐笔流水按现有接口分页加载,不一次性拉取全量数据。
|
||||
- 针对弹窗场景改为上方大图谱、下方逐笔流水明细布局,避免详情栏挤压图谱画布。
|
||||
- 资金边金额标签改为“万/亿”短格式,选中边时高亮当前边和两端节点,其余关系降透明。
|
||||
- 单边明细不再展示“返回全量”按钮,改为点击图谱画布空白区域恢复到刚进入“资金流向”页签时的全量图谱第一眼状态。
|
||||
- 空白点击恢复全量时同步清除选中边、选中节点、逐笔流水数据、明细加载状态和 ECharts 内部强调状态,避免画布残留单条资金边高亮。
|
||||
- 空白点击监听仅绑定资金流向图谱画布,不影响下方逐笔流水表格和分页操作。
|
||||
- 逐笔流水接口增加请求序号校验,返回全量后丢弃旧选中边的异步响应,避免旧请求回写导致明细区再次出现。
|
||||
- 弹窗资金流向明细隐藏累计金额、交易笔数、关系三张汇总卡片,减少重复信息占用空间。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 前端页面:结果总览“查看详情”弹窗中的“资金流向”页签。
|
||||
- 复用组件:`FundGraphSection` 新增弹窗场景可配置项,默认行为不变。
|
||||
- 不影响专项排查页图谱展示,不修改后端接口和数据库。
|
||||
|
||||
## 验证情况
|
||||
|
||||
- 前端命令执行前已尝试 `nvm use`,当前环境未识别 `nvm`;实际 Node 版本为 `v22.22.0`,npm 版本为 `10.9.4`。
|
||||
- 已多次执行 `npm run build:prod`,构建通过,仅保留现有资源体积提示。
|
||||
- 已使用真实页面验证 `http://localhost/ccdiProject/detail/90342?tab=overview`:在结果总览点击“查看详情”,切换“资金流向”,选中交易笔数最多的资金边。
|
||||
- 多笔金额场景验证结果:当前图谱 19 条资金边,选中边为“彭静勇 → 张建强”,交易笔数 5,逐笔流水表格显示 5 条并分页显示“共 5 条”。
|
||||
- 展示验证结果:旧“弹窗速览”提示和“项目分析弹窗仅展示汇总信息”说明均不再出现;金额标签压缩为 `2.22万, 5笔`;汇总卡片已隐藏;逐笔流水展示在图谱下方,未裁切图谱区域。
|
||||
- 恢复全量验证结果:选中“彭静勇 → 张建强”后展示 5 条逐笔流水,其余 18 条资金边降透明;点击图谱空白区域后,旧明细请求未回写,选中边、选中节点、逐笔流水数据和明细总数均清空;图表仍展示 15 个图形节点、19 条资金边,边样式恢复为 opacity `0.9`、width `1.9`,不再残留单条高亮边。
|
||||
- 页面截图已保存到 `output/browser-use/project-analysis-fund-flow-final-graph.png`、`output/browser-use/project-analysis-fund-flow-final-detail.png`、`output/browser-use/project-analysis-fund-flow-reset-all.png`、`output/browser-use/project-analysis-fund-flow-reset-first-view.png` 和 `output/browser-use/project-analysis-fund-flow-blank-click-reset.png`。
|
||||
@@ -0,0 +1,22 @@
|
||||
# 项目上传数据删除暂不调用流水分析平台实施记录
|
||||
|
||||
## 修改背景
|
||||
|
||||
项目管理-上传数据页面点击删除后,当前阶段要求后端先不调用流水分析平台删除文件接口。
|
||||
|
||||
## 修改内容
|
||||
|
||||
- 调整 `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiFileUploadServiceImpl.java`。
|
||||
- 在 `deleteFileUploadRecord` 方法中注释 `DeleteFilesRequest` 构造、`lsfxClient.deleteFiles(request)` 调用以及平台删除失败判断。
|
||||
- 保留本地删除后续逻辑:删除本地银行流水记录、刷新项目目标人数、更新上传记录状态为 `deleted`、触发项目重新打标。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 仅影响项目管理-上传数据页面按记录删除上传文件时的后端处理链路。
|
||||
- 不影响流水分析平台客户端 `LsfxAnalysisClient.deleteFiles` 本身,也不影响测试 Controller 中直接调用流水分析删除接口的能力。
|
||||
- 删除操作不再依赖流水分析平台删除接口返回结果,本地记录删除与重新打标逻辑保持原有执行顺序。
|
||||
|
||||
## 验证情况
|
||||
|
||||
- 已执行 `mvn -pl ccdi-project -am -DskipTests compile`,编译通过。
|
||||
- 本次为后端链路调整,未改动前端页面。
|
||||
@@ -0,0 +1,25 @@
|
||||
# 风险明细列表分页选择器实施记录
|
||||
|
||||
## 保存路径确认
|
||||
|
||||
- 本实施记录保存于 `docs/reports/implementation/`,符合项目实施记录目录规范。
|
||||
|
||||
## 修改内容
|
||||
|
||||
- 在 `RiskDetailSection.vue` 中为风险明细卡片内的三个列表统一启用分页条数选择器。
|
||||
- 新增风险明细列表共享分页选项 `[5, 10, 20, 50]`,首屏默认仍保持每页 5 条。
|
||||
- 涉疑交易明细、员工负面征信信息、异常账户人员信息翻页或切换每页条数时,均使用当前分页参数重新请求对应接口。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 仅影响项目详情结果总览中的风险明细前端展示与分页请求参数。
|
||||
- 不涉及后端接口、数据库结构、导出逻辑和风险模型计算逻辑。
|
||||
|
||||
## 验证情况
|
||||
|
||||
- 已通过 `nvm use` 切换到 Node `v14.21.3` 后执行风险明细分页相关前端源码断言测试。
|
||||
- 已执行 `npm run build:prod`,构建通过,仅保留项目既有资源体积 warning。
|
||||
- 已在应用内浏览器打开真实项目详情结果总览页面,验证风险明细分页选择器展示与切换:
|
||||
- `0430test` 项目涉疑交易明细从 `5条/页` 切换为 `10条/页` 后展示 10 行。
|
||||
- `0430test` 项目异常账户人员信息从 `5条/页` 切换为 `10条/页` 后展示 6 行。
|
||||
- 验证过程中未发现浏览器控制台错误。
|
||||
@@ -0,0 +1,14 @@
|
||||
# 风险模型导出按钮移除实施记录
|
||||
|
||||
## 修改内容
|
||||
|
||||
- 移除项目详情页“风险模型”模块中“模型预警次数统计”标题右侧的“导出”按钮。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 仅调整前端展示层文件:`ruoyi-ui/src/views/ccdiProject/components/detail/RiskModelSection.vue`。
|
||||
- 不涉及后端接口、数据结构、权限菜单或业务统计逻辑变更。
|
||||
|
||||
## 验证情况
|
||||
|
||||
- 已执行静态检索确认该模块内目标“导出”按钮已移除。
|
||||
@@ -375,7 +375,7 @@ export default {
|
||||
}
|
||||
|
||||
.project-analysis-header {
|
||||
padding: 28px 40px 24px;
|
||||
padding: 38px 54px 34px;
|
||||
border-bottom: 1px solid #dde3ec;
|
||||
background: #ffffff;
|
||||
}
|
||||
@@ -393,15 +393,15 @@ export default {
|
||||
|
||||
.project-analysis-header__eyebrow {
|
||||
color: #65758d;
|
||||
font-size: 15px;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.project-analysis-header__title {
|
||||
margin-top: 18px;
|
||||
margin-top: 24px;
|
||||
color: #101a2b;
|
||||
font-size: 28px;
|
||||
font-size: 34px;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
@@ -432,10 +432,10 @@ export default {
|
||||
.project-analysis-workspace {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 36px;
|
||||
min-height: 640px;
|
||||
max-height: calc(92vh - 150px);
|
||||
padding: 20px 40px 28px;
|
||||
gap: 56px;
|
||||
min-height: 720px;
|
||||
max-height: calc(96vh - 168px);
|
||||
padding: 28px 54px 42px;
|
||||
overflow: auto;
|
||||
background: #ffffff;
|
||||
}
|
||||
@@ -447,15 +447,15 @@ export default {
|
||||
}
|
||||
|
||||
.project-analysis-layout__sidebar {
|
||||
flex: 0 0 34%;
|
||||
max-width: 460px;
|
||||
flex: 0 0 38%;
|
||||
max-width: 520px;
|
||||
}
|
||||
|
||||
.project-analysis-layout__main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
border-left: 1px solid #dde3ec;
|
||||
padding-left: 36px;
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.project-analysis-layout__alert {
|
||||
@@ -495,12 +495,12 @@ export default {
|
||||
}
|
||||
|
||||
.el-tabs__item {
|
||||
height: 52px;
|
||||
padding: 0 24px !important;
|
||||
height: 58px;
|
||||
padding: 0 32px !important;
|
||||
color: #2a374a;
|
||||
font-size: 16px;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
line-height: 52px;
|
||||
line-height: 58px;
|
||||
}
|
||||
|
||||
.el-tabs__item.is-active {
|
||||
@@ -514,7 +514,7 @@ export default {
|
||||
}
|
||||
|
||||
.el-tabs__content {
|
||||
padding-top: 20px;
|
||||
padding-top: 28px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="project-analysis-fund-graph">
|
||||
<div class="project-analysis-fund-graph" :class="rootClasses">
|
||||
<fund-graph-section
|
||||
ref="fundGraphSection"
|
||||
:initial-keyword="defaultKeyword"
|
||||
@@ -7,8 +7,12 @@
|
||||
:auto-scroll="false"
|
||||
:hide-header="true"
|
||||
:graph-tabs="graphTabs"
|
||||
:layout-scale="0.94"
|
||||
:show-edge-detail-table="false"
|
||||
:layout-scale="1.03"
|
||||
:show-edge-detail-table="true"
|
||||
:edge-detail-table-height="220"
|
||||
compact-graph-labels
|
||||
:show-edge-metrics="!isFundOnly"
|
||||
:stacked-fund-detail-pane="isFundOnly"
|
||||
hide-empty-detail-pane
|
||||
/>
|
||||
</div>
|
||||
@@ -46,6 +50,15 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isFundOnly() {
|
||||
return this.initialGraphTab === "fund" && this.graphTabs.length === 2;
|
||||
},
|
||||
rootClasses() {
|
||||
return {
|
||||
"project-analysis-fund-graph--fund": this.isFundOnly,
|
||||
"project-analysis-fund-graph--relation": !this.isFundOnly,
|
||||
};
|
||||
},
|
||||
defaultKeyword() {
|
||||
return (this.modelSummary && this.modelSummary.staffIdCard)
|
||||
|| (this.person && (this.person.idNo || this.person.staffIdCard || this.person.staffName || this.person.name))
|
||||
@@ -76,7 +89,7 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.project-analysis-fund-graph {
|
||||
min-height: 520px;
|
||||
min-height: 780px;
|
||||
|
||||
::v-deep .graph-analysis-section {
|
||||
margin-top: 0;
|
||||
@@ -84,28 +97,44 @@ export default {
|
||||
}
|
||||
|
||||
::v-deep .fund-workbench {
|
||||
grid-template-columns: minmax(0, 1fr) 316px;
|
||||
max-width: 1000px;
|
||||
min-height: 450px;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
max-width: 100%;
|
||||
min-height: 760px;
|
||||
}
|
||||
|
||||
::v-deep .fund-workbench.fund-workbench--single {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
max-width: 760px;
|
||||
max-width: 100%;
|
||||
min-height: 560px;
|
||||
}
|
||||
|
||||
::v-deep .fund-workbench--stacked {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
::v-deep .fund-left-pane {
|
||||
padding: 10px 12px 12px;
|
||||
padding: 14px 16px 16px;
|
||||
overflow: hidden;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
::v-deep .fund-right-pane {
|
||||
padding: 12px;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 14px;
|
||||
background: #ffffff;
|
||||
border-top: 1px solid #dce7f2;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
::v-deep .graph-canvas-wrap,
|
||||
::v-deep .detail-empty {
|
||||
::v-deep .graph-canvas-wrap {
|
||||
height: auto;
|
||||
min-height: 360px;
|
||||
min-height: 520px;
|
||||
}
|
||||
|
||||
::v-deep .detail-empty {
|
||||
min-height: 240px;
|
||||
}
|
||||
|
||||
::v-deep .query-row {
|
||||
@@ -124,6 +153,21 @@ export default {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
::v-deep .detail-table .el-table__cell {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
::v-deep .pagination-container {
|
||||
margin-top: 8px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::v-deep .pagination-container .el-pagination {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
::v-deep .query-row {
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
@@ -131,32 +175,73 @@ export default {
|
||||
|
||||
::v-deep .graph-shell {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
::v-deep .graph-canvas {
|
||||
min-height: 360px;
|
||||
min-height: 520px;
|
||||
}
|
||||
|
||||
::v-deep .detail-title {
|
||||
font-size: 15px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
::v-deep .node-field-row,
|
||||
::v-deep .edge-metrics span,
|
||||
::v-deep .edge-metrics strong,
|
||||
::v-deep .detail-subtitle {
|
||||
font-size: 11px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.project-analysis-fund-graph--relation {
|
||||
min-height: 520px;
|
||||
|
||||
::v-deep .fund-workbench {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
max-width: 760px;
|
||||
min-height: 450px;
|
||||
}
|
||||
|
||||
::v-deep .graph-canvas-wrap,
|
||||
::v-deep .detail-empty {
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
::v-deep .graph-canvas {
|
||||
min-height: 360px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1440px) {
|
||||
.project-analysis-fund-graph {
|
||||
.project-analysis-fund-graph--fund {
|
||||
::v-deep .fund-workbench {
|
||||
grid-template-columns: minmax(0, 1fr) 300px;
|
||||
max-width: 940px;
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
::v-deep .fund-workbench.fund-workbench--single {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
::v-deep .graph-canvas-wrap {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
::v-deep .detail-empty {
|
||||
min-height: 220px;
|
||||
}
|
||||
|
||||
::v-deep .graph-canvas {
|
||||
min-height: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.project-analysis-fund-graph--relation {
|
||||
::v-deep .fund-workbench {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
@@ -120,8 +120,8 @@
|
||||
:total="suspiciousTotal"
|
||||
:page.sync="suspiciousPageNum"
|
||||
:limit.sync="suspiciousPageSize"
|
||||
:page-sizes="[5]"
|
||||
layout="total, prev, pager, next, jumper"
|
||||
:page-sizes="riskDetailPageSizeOptions"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@pagination="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
@@ -174,8 +174,8 @@
|
||||
:total="employeeCreditNegativeTotal"
|
||||
:page.sync="employeeCreditNegativePageNum"
|
||||
:limit.sync="employeeCreditNegativePageSize"
|
||||
:page-sizes="[5]"
|
||||
layout="total, prev, pager, next, jumper"
|
||||
:page-sizes="riskDetailPageSizeOptions"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@pagination="handleEmployeeCreditNegativePageChange"
|
||||
/>
|
||||
</div>
|
||||
@@ -209,8 +209,8 @@
|
||||
:total="abnormalAccountTotal"
|
||||
:page.sync="abnormalAccountPageNum"
|
||||
:limit.sync="abnormalAccountPageSize"
|
||||
:page-sizes="[5]"
|
||||
layout="total, prev, pager, next, jumper"
|
||||
:page-sizes="riskDetailPageSizeOptions"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@pagination="handleAbnormalAccountPageChange"
|
||||
/>
|
||||
</div>
|
||||
@@ -352,6 +352,8 @@ const SUSPICIOUS_TYPE_OPTIONS = [
|
||||
{ value: "NAME_LIST", label: "名单库命中" },
|
||||
{ value: "MODEL_RULE", label: "模型规则命中" },
|
||||
];
|
||||
const DEFAULT_RISK_DETAIL_PAGE_SIZE = 5;
|
||||
const RISK_DETAIL_PAGE_SIZE_OPTIONS = [5, 10, 20, 50];
|
||||
|
||||
const normalizeSuspiciousType = (value) => (
|
||||
SUSPICIOUS_TYPE_OPTIONS.some((item) => item.value === value) ? value : "ALL"
|
||||
@@ -465,17 +467,17 @@ export default {
|
||||
detailData: createEmptyDetailData(),
|
||||
currentSuspiciousType: "ALL",
|
||||
suspiciousPageNum: 1,
|
||||
suspiciousPageSize: 5,
|
||||
suspiciousPageSize: DEFAULT_RISK_DETAIL_PAGE_SIZE,
|
||||
suspiciousTotal: 0,
|
||||
suspiciousTransactionList: [],
|
||||
employeeCreditNegativeLoading: false,
|
||||
employeeCreditNegativePageNum: 1,
|
||||
employeeCreditNegativePageSize: 5,
|
||||
employeeCreditNegativePageSize: DEFAULT_RISK_DETAIL_PAGE_SIZE,
|
||||
employeeCreditNegativeTotal: 0,
|
||||
employeeCreditNegativeList: [],
|
||||
abnormalAccountLoading: false,
|
||||
abnormalAccountPageNum: 1,
|
||||
abnormalAccountPageSize: 5,
|
||||
abnormalAccountPageSize: DEFAULT_RISK_DETAIL_PAGE_SIZE,
|
||||
abnormalAccountTotal: 0,
|
||||
abnormalAccountList: [],
|
||||
projectId: null,
|
||||
@@ -483,6 +485,9 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
riskDetailPageSizeOptions() {
|
||||
return RISK_DETAIL_PAGE_SIZE_OPTIONS;
|
||||
},
|
||||
suspiciousTypeOptions() {
|
||||
return SUSPICIOUS_TYPE_OPTIONS;
|
||||
},
|
||||
@@ -499,13 +504,13 @@ export default {
|
||||
this.projectId = value && value.projectId ? value.projectId : null;
|
||||
this.currentSuspiciousType = normalizeSuspiciousType(value && value.suspiciousType);
|
||||
this.suspiciousPageNum = 1;
|
||||
this.suspiciousPageSize = 5;
|
||||
this.suspiciousPageSize = DEFAULT_RISK_DETAIL_PAGE_SIZE;
|
||||
this.suspiciousTotal = Number(value && value.total) || 0;
|
||||
this.employeeCreditNegativePageNum = 1;
|
||||
this.employeeCreditNegativePageSize = 5;
|
||||
this.employeeCreditNegativePageSize = DEFAULT_RISK_DETAIL_PAGE_SIZE;
|
||||
this.employeeCreditNegativeTotal = Number(value && value.employeeCreditNegativeTotal) || 0;
|
||||
this.abnormalAccountPageNum = 1;
|
||||
this.abnormalAccountPageSize = 5;
|
||||
this.abnormalAccountPageSize = DEFAULT_RISK_DETAIL_PAGE_SIZE;
|
||||
this.abnormalAccountTotal = 0;
|
||||
this.abnormalAccountList = [];
|
||||
const rows = Array.isArray(value && value.suspiciousTransactionList)
|
||||
@@ -531,7 +536,7 @@ export default {
|
||||
this.suspiciousPageNum = pageInfo;
|
||||
} else {
|
||||
this.suspiciousPageNum = pageInfo.page;
|
||||
this.suspiciousPageSize = 5;
|
||||
this.suspiciousPageSize = pageInfo.limit || this.suspiciousPageSize;
|
||||
}
|
||||
await this.loadSuspiciousTransactions();
|
||||
},
|
||||
@@ -540,7 +545,7 @@ export default {
|
||||
this.employeeCreditNegativePageNum = pageInfo;
|
||||
} else {
|
||||
this.employeeCreditNegativePageNum = pageInfo.page;
|
||||
this.employeeCreditNegativePageSize = 5;
|
||||
this.employeeCreditNegativePageSize = pageInfo.limit || this.employeeCreditNegativePageSize;
|
||||
}
|
||||
await this.loadEmployeeCreditNegative();
|
||||
},
|
||||
@@ -549,7 +554,7 @@ export default {
|
||||
this.abnormalAccountPageNum = pageInfo;
|
||||
} else {
|
||||
this.abnormalAccountPageNum = pageInfo.page;
|
||||
this.abnormalAccountPageSize = 5;
|
||||
this.abnormalAccountPageSize = pageInfo.limit || this.abnormalAccountPageSize;
|
||||
}
|
||||
await this.loadAbnormalAccountPeople();
|
||||
},
|
||||
@@ -567,7 +572,7 @@ export default {
|
||||
projectId: this.projectId,
|
||||
suspiciousType: this.currentSuspiciousType,
|
||||
pageNum: this.suspiciousPageNum,
|
||||
pageSize: 5,
|
||||
pageSize: this.suspiciousPageSize,
|
||||
});
|
||||
const data = (response && response.data) || {};
|
||||
this.suspiciousTotal = Number(data.total) || 0;
|
||||
@@ -593,7 +598,7 @@ export default {
|
||||
const response = await getOverviewEmployeeCreditNegative({
|
||||
projectId: this.projectId,
|
||||
pageNum: this.employeeCreditNegativePageNum,
|
||||
pageSize: 5,
|
||||
pageSize: this.employeeCreditNegativePageSize,
|
||||
});
|
||||
const data = (response && response.data) || {};
|
||||
this.employeeCreditNegativeList = Array.isArray(data.rows) ? data.rows : [];
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
<div class="block-title">模型预警次数统计</div>
|
||||
<div class="block-subtitle">按模型汇总预警命中次数与涉及人数</div>
|
||||
</div>
|
||||
<el-button size="mini" type="text">导出</el-button>
|
||||
</div>
|
||||
|
||||
<div v-loading="cardLoading" class="model-card-grid">
|
||||
|
||||
@@ -37,7 +37,10 @@
|
||||
<div
|
||||
v-show="activeGraphTab === 'fund'"
|
||||
class="fund-workbench"
|
||||
:class="{ 'fund-workbench--single': shouldHideFundDetailPane }"
|
||||
:class="{
|
||||
'fund-workbench--single': shouldHideFundDetailPane,
|
||||
'fund-workbench--stacked': stackedFundDetailPane,
|
||||
}"
|
||||
>
|
||||
<div class="fund-left-pane">
|
||||
<div class="query-row">
|
||||
@@ -155,7 +158,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="edge-metrics">
|
||||
<div v-if="showEdgeMetrics" class="edge-metrics">
|
||||
<div>
|
||||
<span>累计金额</span>
|
||||
<strong>{{ formatMoney(selectedEdge.totalAmount) }}</strong>
|
||||
@@ -199,7 +202,7 @@
|
||||
v-loading="detailLoading"
|
||||
:data="edgeDetails"
|
||||
size="mini"
|
||||
height="342"
|
||||
:height="edgeDetailTableHeight"
|
||||
class="detail-table"
|
||||
>
|
||||
<template slot="empty">
|
||||
@@ -512,6 +515,22 @@ export default {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
edgeDetailTableHeight: {
|
||||
type: Number,
|
||||
default: 342,
|
||||
},
|
||||
compactGraphLabels: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showEdgeMetrics: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
stackedFundDetailPane: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
hideEmptyDetailPane: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -521,6 +540,7 @@ export default {
|
||||
return {
|
||||
activeGraphTab: this.initialGraphTab,
|
||||
chart: null,
|
||||
chartBlankClickHandler: null,
|
||||
relationChart: null,
|
||||
graphLoading: false,
|
||||
relationGraphLoading: false,
|
||||
@@ -532,6 +552,7 @@ export default {
|
||||
selectedRelationNode: null,
|
||||
edgeDetails: [],
|
||||
detailTotal: 0,
|
||||
edgeDetailRequestSeq: 0,
|
||||
expandedObjectKeys: [],
|
||||
contextMenu: {
|
||||
visible: false,
|
||||
@@ -707,8 +728,7 @@ export default {
|
||||
this.resizeRelationChart();
|
||||
});
|
||||
} else if (this.chart) {
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
this.destroyFundChart();
|
||||
}
|
||||
},
|
||||
initialKeyword: {
|
||||
@@ -733,10 +753,7 @@ export default {
|
||||
window.removeEventListener("resize", this.resizeChart);
|
||||
window.removeEventListener("resize", this.resizeRelationChart);
|
||||
document.removeEventListener("click", this.hideContextMenu);
|
||||
if (this.chart) {
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
}
|
||||
this.destroyFundChart();
|
||||
if (this.relationChart) {
|
||||
this.relationChart.dispose();
|
||||
this.relationChart = null;
|
||||
@@ -1001,6 +1018,17 @@ export default {
|
||||
}
|
||||
if (!this.chart) {
|
||||
this.chart = echarts.init(this.$refs.chart);
|
||||
this.chartBlankClickHandler = (event) => {
|
||||
if (event && event.target) {
|
||||
return;
|
||||
}
|
||||
if (!this.selectedNode && !this.selectedEdge) {
|
||||
return;
|
||||
}
|
||||
this.hideContextMenu();
|
||||
this.resetFundSelection();
|
||||
};
|
||||
this.chart.getZr().on("click", this.chartBlankClickHandler);
|
||||
this.chart.on("click", (params) => {
|
||||
this.hideContextMenu();
|
||||
if (params.dataType === "edge" && params.data && params.data.raw) {
|
||||
@@ -1034,6 +1062,18 @@ export default {
|
||||
}
|
||||
this.renderChart();
|
||||
},
|
||||
destroyFundChart() {
|
||||
if (!this.chart) {
|
||||
this.chartBlankClickHandler = null;
|
||||
return;
|
||||
}
|
||||
if (this.chartBlankClickHandler && this.chart.getZr) {
|
||||
this.chart.getZr().off("click", this.chartBlankClickHandler);
|
||||
}
|
||||
this.chartBlankClickHandler = null;
|
||||
this.chart.dispose();
|
||||
this.chart = null;
|
||||
},
|
||||
initRelationChart() {
|
||||
if (!this.$refs.relationChart) {
|
||||
return;
|
||||
@@ -1212,23 +1252,35 @@ export default {
|
||||
}],
|
||||
}, true);
|
||||
},
|
||||
renderChart() {
|
||||
renderChart(options = {}) {
|
||||
if (!this.chart) {
|
||||
return;
|
||||
}
|
||||
const resetViewport = !!options.resetViewport;
|
||||
this.syncChartSize();
|
||||
if (resetViewport) {
|
||||
this.chart.clear();
|
||||
}
|
||||
const centerObjectKey = this.graph.centerNode && this.graph.centerNode.objectKey;
|
||||
const rawNodes = this.graph.nodes || [];
|
||||
const rawEdges = this.graph.edges || [];
|
||||
const chartWidth = this.chart.getWidth ? this.chart.getWidth() : 900;
|
||||
const chartHeight = this.chart.getHeight ? this.chart.getHeight() : 540;
|
||||
const nodeLayout = this.buildNodeLayout(rawNodes, rawEdges, centerObjectKey, chartWidth, chartHeight);
|
||||
const selectedEdge = this.selectedEdge;
|
||||
const hasFundSelection = !!(this.selectedNode || selectedEdge);
|
||||
const nodes = rawNodes.map((node) => {
|
||||
const isCenter = node.objectKey === centerObjectKey;
|
||||
const isFamily = !!node.relationType;
|
||||
const isExpanded = this.expandedObjectKeys.includes(node.objectKey);
|
||||
const category = isCenter ? 0 : isFamily ? 1 : isExpanded ? 3 : 2;
|
||||
const layout = nodeLayout.get(node.nodeKey) || {};
|
||||
const displayName = this.compactGraphLabels
|
||||
? this.truncateGraphText(node.nodeName, isCenter ? 8 : 7)
|
||||
: (node.nodeName || "-");
|
||||
const isSelectedNode = this.selectedNode && this.selectedNode.objectKey === node.objectKey;
|
||||
const inSelectedEdge = this.isNodeInSelectedEdge(node, selectedEdge);
|
||||
const shouldDim = selectedEdge && !inSelectedEdge;
|
||||
return {
|
||||
id: node.nodeKey,
|
||||
name: node.nodeName,
|
||||
@@ -1240,7 +1292,7 @@ export default {
|
||||
fixed: true,
|
||||
category,
|
||||
raw: node,
|
||||
itemStyle: this.getNodeStyle(category, this.selectedNode && this.selectedNode.objectKey === node.objectKey),
|
||||
itemStyle: this.getNodeStyle(category, isSelectedNode || inSelectedEdge, shouldDim),
|
||||
emphasis: {
|
||||
scale: isCenter ? 1.08 : 1.12,
|
||||
itemStyle: {
|
||||
@@ -1257,13 +1309,13 @@ export default {
|
||||
show: true,
|
||||
position: "bottom",
|
||||
distance: isCenter ? 8 : 6,
|
||||
color: "#1b1f2a",
|
||||
color: shouldDim ? "#8794a6" : "#1b1f2a",
|
||||
fontSize: isCenter ? 14 : 13,
|
||||
fontWeight: isCenter ? 700 : 500,
|
||||
formatter: isFamily ? `{name|${node.nodeName || "-"} }\n{tag|${node.relationType}}` : "{b}",
|
||||
formatter: isFamily ? `{name|${displayName} }\n{tag|${node.relationType}}` : displayName,
|
||||
rich: {
|
||||
name: {
|
||||
color: "#1b1f2a",
|
||||
color: shouldDim ? "#8794a6" : "#1b1f2a",
|
||||
fontWeight: isCenter ? 700 : 500,
|
||||
fontSize: isCenter ? 14 : 13,
|
||||
lineHeight: 17,
|
||||
@@ -1280,6 +1332,8 @@ export default {
|
||||
const edgeCurves = this.buildEdgeCurves(rawEdges);
|
||||
const edges = rawEdges.map((edge, index) => {
|
||||
const isOut = String(edge.direction) === "1";
|
||||
const selected = this.isSelectedEdge(edge, selectedEdge);
|
||||
const shouldDim = selectedEdge && !selected;
|
||||
return {
|
||||
source: edge.fromKey,
|
||||
target: edge.toKey,
|
||||
@@ -1293,8 +1347,8 @@ export default {
|
||||
},
|
||||
lineStyle: {
|
||||
color: this.isExpandedEdge(edge) ? (isOut ? "#4f78ab" : "#3a8f83") : (isOut ? "#245f95" : "#1c7f73"),
|
||||
opacity: this.isExpandedEdge(edge) ? 0.72 : 0.9,
|
||||
width: this.isExpandedEdge(edge) ? 1.5 : 1.9,
|
||||
opacity: shouldDim ? 0.22 : selected ? 1 : this.isExpandedEdge(edge) ? 0.72 : 0.9,
|
||||
width: selected ? 2.8 : this.isExpandedEdge(edge) ? 1.5 : 1.9,
|
||||
curveness: edgeCurves[index],
|
||||
type: this.isManualEdge(edge) ? "dashed" : "solid",
|
||||
},
|
||||
@@ -1364,23 +1418,23 @@ export default {
|
||||
edgeLabel: {
|
||||
position: "middle",
|
||||
rotate: 0,
|
||||
distance: 22,
|
||||
color: "#4b5a6a",
|
||||
fontSize: 11,
|
||||
distance: this.compactGraphLabels ? 18 : 22,
|
||||
color: selectedEdge ? "#516070" : "#4b5a6a",
|
||||
fontSize: this.compactGraphLabels ? 10 : 11,
|
||||
fontWeight: 500,
|
||||
fontFamily: "'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif",
|
||||
backgroundColor: "rgba(255,255,255,0.92)",
|
||||
backgroundColor: this.compactGraphLabels ? "rgba(255,255,255,0.86)" : "rgba(255,255,255,0.92)",
|
||||
borderColor: "rgba(205, 216, 228, 0.92)",
|
||||
borderWidth: 1,
|
||||
borderRadius: 8,
|
||||
padding: [3, 7],
|
||||
padding: this.compactGraphLabels ? [2, 5] : [3, 7],
|
||||
},
|
||||
edgeLabelLayout: (params) => this.layoutEdgeLabel(params, nodeLayout, rawEdges, edgeCurves),
|
||||
labelLayout: { hideOverlap: false },
|
||||
data: nodes.concat(boundaryNodes),
|
||||
links: edges,
|
||||
emphasis: {
|
||||
focus: "adjacency",
|
||||
focus: hasFundSelection ? "none" : "adjacency",
|
||||
scale: true,
|
||||
lineStyle: {
|
||||
opacity: 0.92,
|
||||
@@ -1389,7 +1443,9 @@ export default {
|
||||
},
|
||||
}],
|
||||
}, true);
|
||||
if (!resetViewport) {
|
||||
this.resizeChart();
|
||||
}
|
||||
},
|
||||
buildBoundaryNodes(chartWidth, chartHeight) {
|
||||
const padding = 56;
|
||||
@@ -1631,10 +1687,10 @@ export default {
|
||||
}
|
||||
return PROXY_SYMBOL;
|
||||
},
|
||||
getNodeStyle(category, selected) {
|
||||
getNodeStyle(category, selected, dimmed = false) {
|
||||
const isCenter = category === 0;
|
||||
return {
|
||||
opacity: category === 3 ? 0.88 : 1,
|
||||
opacity: dimmed ? 0.42 : category === 3 ? 0.88 : 1,
|
||||
shadowBlur: selected ? 16 : isCenter ? 10 : 6,
|
||||
shadowOffsetY: selected ? 4 : 2,
|
||||
shadowColor: selected ? "rgba(42, 111, 174, 0.24)" : "rgba(45, 84, 124, 0.12)",
|
||||
@@ -1644,6 +1700,7 @@ export default {
|
||||
this.selectedEdge = edge;
|
||||
this.selectedNode = null;
|
||||
this.detailQuery.pageNum = 1;
|
||||
this.renderChart();
|
||||
if (this.isManualEdge(edge)) {
|
||||
this.edgeDetails = [];
|
||||
this.detailTotal = 0;
|
||||
@@ -1655,7 +1712,9 @@ export default {
|
||||
if (!this.selectedEdge) {
|
||||
return;
|
||||
}
|
||||
if (!this.selectedEdge.fromKey || !this.selectedEdge.toKey) {
|
||||
const requestSeq = ++this.edgeDetailRequestSeq;
|
||||
const selectedEdge = this.selectedEdge;
|
||||
if (!selectedEdge.fromKey || !selectedEdge.toKey) {
|
||||
this.edgeDetails = [];
|
||||
this.detailTotal = 0;
|
||||
this.$message.warning("当前边缺少明细查询参数");
|
||||
@@ -1664,27 +1723,35 @@ export default {
|
||||
this.detailLoading = true;
|
||||
try {
|
||||
const response = await getFundGraphEdgeDetail({
|
||||
fromKey: this.selectedEdge.fromKey,
|
||||
toKey: this.selectedEdge.toKey,
|
||||
direction: this.selectedEdge.direction,
|
||||
fromKey: selectedEdge.fromKey,
|
||||
toKey: selectedEdge.toKey,
|
||||
direction: selectedEdge.direction,
|
||||
transactionStartTime: this.query.transactionStartTime,
|
||||
transactionEndTime: this.query.transactionEndTime,
|
||||
pageNum: this.detailQuery.pageNum,
|
||||
pageSize: this.detailQuery.pageSize,
|
||||
});
|
||||
if (requestSeq !== this.edgeDetailRequestSeq || this.selectedEdge !== selectedEdge) {
|
||||
return;
|
||||
}
|
||||
this.edgeDetails = response.rows || [];
|
||||
this.detailTotal = response.total || 0;
|
||||
} catch (error) {
|
||||
if (requestSeq !== this.edgeDetailRequestSeq || this.selectedEdge !== selectedEdge) {
|
||||
return;
|
||||
}
|
||||
this.edgeDetails = [];
|
||||
this.detailTotal = 0;
|
||||
this.$message.error("流水明细查询失败");
|
||||
console.error("流水明细查询失败", {
|
||||
error,
|
||||
selectedEdge: this.selectedEdge,
|
||||
selectedEdge,
|
||||
});
|
||||
} finally {
|
||||
if (requestSeq === this.edgeDetailRequestSeq && this.selectedEdge === selectedEdge) {
|
||||
this.detailLoading = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
mergeGraph(currentGraph, nextGraph) {
|
||||
const nodeMap = new Map();
|
||||
@@ -1711,6 +1778,26 @@ export default {
|
||||
isManualEdge(edge) {
|
||||
return !!(edge && edge.sourceType === "MANUAL");
|
||||
},
|
||||
isSelectedEdge(edge, selectedEdge) {
|
||||
if (!edge || !selectedEdge) {
|
||||
return false;
|
||||
}
|
||||
if (edge.edgeKey && selectedEdge.edgeKey) {
|
||||
return edge.edgeKey === selectedEdge.edgeKey;
|
||||
}
|
||||
return edge.fromKey === selectedEdge.fromKey
|
||||
&& edge.toKey === selectedEdge.toKey
|
||||
&& String(edge.direction || "") === String(selectedEdge.direction || "");
|
||||
},
|
||||
isNodeInSelectedEdge(node, selectedEdge) {
|
||||
if (!node || !selectedEdge) {
|
||||
return false;
|
||||
}
|
||||
return node.nodeKey === selectedEdge.fromKey
|
||||
|| node.nodeKey === selectedEdge.toKey
|
||||
|| node.objectKey === selectedEdge.fromObjectKey
|
||||
|| node.objectKey === selectedEdge.toObjectKey;
|
||||
},
|
||||
buildRelationNodeLayout(nodes, edges, centerObjectKey, chartWidth, chartHeight) {
|
||||
const layout = new Map();
|
||||
if (!nodes.length) {
|
||||
@@ -1860,10 +1947,30 @@ export default {
|
||||
this.queryByNode(node);
|
||||
},
|
||||
clearSelection() {
|
||||
this.edgeDetailRequestSeq += 1;
|
||||
this.selectedNode = null;
|
||||
this.selectedEdge = null;
|
||||
this.edgeDetails = [];
|
||||
this.detailTotal = 0;
|
||||
this.detailLoading = false;
|
||||
},
|
||||
resetFundSelection() {
|
||||
this.clearChartEmphasis();
|
||||
this.clearSelection();
|
||||
this.renderChart({ resetViewport: true });
|
||||
this.$nextTick(() => {
|
||||
this.clearChartEmphasis();
|
||||
});
|
||||
},
|
||||
clearChartEmphasis() {
|
||||
if (!this.chart) {
|
||||
return;
|
||||
}
|
||||
this.chart.dispatchAction({ type: "hideTip" });
|
||||
this.chart.dispatchAction({
|
||||
type: "downplay",
|
||||
seriesIndex: 0,
|
||||
});
|
||||
},
|
||||
resizeChart() {
|
||||
this.$nextTick(() => {
|
||||
@@ -1922,8 +2029,29 @@ export default {
|
||||
formatEdgeLabel(edge) {
|
||||
const amount = Number(edge && edge.totalAmount ? edge.totalAmount : 0);
|
||||
const count = Number(edge && edge.transactionCount ? edge.transactionCount : 0);
|
||||
if (this.compactGraphLabels) {
|
||||
return `${this.formatCompactMoney(amount)}, ${count}笔`;
|
||||
}
|
||||
return `${amount.toLocaleString("zh-CN", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}元, ${count}笔`;
|
||||
},
|
||||
formatCompactMoney(value) {
|
||||
const number = Math.abs(Number(value || 0));
|
||||
const sign = Number(value || 0) < 0 ? "-" : "";
|
||||
if (number >= 100000000) {
|
||||
return `${sign}${(number / 100000000).toLocaleString("zh-CN", { maximumFractionDigits: 2 })}亿`;
|
||||
}
|
||||
if (number >= 10000) {
|
||||
return `${sign}${(number / 10000).toLocaleString("zh-CN", { maximumFractionDigits: 2 })}万`;
|
||||
}
|
||||
return `${sign}${number.toLocaleString("zh-CN", { maximumFractionDigits: 0 })}元`;
|
||||
},
|
||||
truncateGraphText(value, maxLength) {
|
||||
const text = String(value || "-");
|
||||
if (text.length <= maxLength) {
|
||||
return text;
|
||||
}
|
||||
return `${text.slice(0, Math.max(1, maxLength - 1))}…`;
|
||||
},
|
||||
formatShortMoney(value) {
|
||||
const number = Number(value || 0);
|
||||
return `${number.toLocaleString("zh-CN", { maximumFractionDigits: 2 })}元`;
|
||||
|
||||
@@ -19,4 +19,8 @@ const source = fs.readFileSync(
|
||||
"abnormalAccountList",
|
||||
"handleAbnormalAccountPageChange",
|
||||
"loadAbnormalAccountPeople",
|
||||
":page-sizes=\"riskDetailPageSizeOptions\"",
|
||||
"layout=\"total, sizes, prev, pager, next, jumper\"",
|
||||
"pageSize: this.abnormalAccountPageSize",
|
||||
"this.abnormalAccountPageSize = pageInfo.limit || this.abnormalAccountPageSize",
|
||||
].forEach((token) => assert(source.includes(token), token));
|
||||
|
||||
@@ -19,4 +19,8 @@ const source = fs.readFileSync(
|
||||
"loadEmployeeCreditNegative",
|
||||
"handleEmployeeCreditNegativePageChange",
|
||||
"getOverviewEmployeeCreditNegative",
|
||||
":page-sizes=\"riskDetailPageSizeOptions\"",
|
||||
"layout=\"total, sizes, prev, pager, next, jumper\"",
|
||||
"pageSize: this.employeeCreditNegativePageSize",
|
||||
"this.employeeCreditNegativePageSize = pageInfo.limit || this.employeeCreditNegativePageSize",
|
||||
].forEach((token) => assert(source.includes(token), token));
|
||||
|
||||
@@ -21,9 +21,11 @@ const source = fs.readFileSync(
|
||||
"摘要 / 交易类型",
|
||||
"异常标签",
|
||||
"<pagination",
|
||||
"pageSize: 5",
|
||||
":page-sizes=\"[5]\"",
|
||||
"layout=\"total, prev, pager, next, jumper\"",
|
||||
"DEFAULT_RISK_DETAIL_PAGE_SIZE",
|
||||
"RISK_DETAIL_PAGE_SIZE_OPTIONS = [5, 10, 20, 50]",
|
||||
":page-sizes=\"riskDetailPageSizeOptions\"",
|
||||
"layout=\"total, sizes, prev, pager, next, jumper\"",
|
||||
"pageSize: this.suspiciousPageSize",
|
||||
].forEach((token) => assert(source.includes(token), token));
|
||||
|
||||
[
|
||||
|
||||
Reference in New Issue
Block a user