feat(customer): 添加客户报备重复检查功能
- 新增 DUP_CHECK_LIMIT 和 DUP_CHECK_MAX_PAGES 常量配置 - 实现三要素重复检查逻辑,支持地址、楼栋号、单元号、房号、姓名、电话等字段组合匹配 - 添加 normalizeText 函数处理文本标准化 - 实现 combinationsOf3 函数生成三个元素的组合 - 添加 checkDuplicateBeforeSubmit 函数执行重复检查 - 在提交前验证重复报备并显示提示信息 - 移除用户页面中的 IsDealer 组件 - 更新用户组件删除实名认证和收货地址相关功能
This commit is contained in:
@@ -26,6 +26,8 @@ const AddShopDealerApply = () => {
|
||||
const [existingApply, setExistingApply] = useState<ShopDealerApply | null>(null)
|
||||
const [referee, setReferee] = useState<ShopDealerUser>()
|
||||
const PROTECTION_DAYS = 15;
|
||||
const DUP_CHECK_LIMIT = 200;
|
||||
const DUP_CHECK_MAX_PAGES = 50;
|
||||
|
||||
// 房号信息:用 dealerCode 存储唯一键,dealerName 存储展示文案
|
||||
const buildHouseKey = (community: string, buildingNo: string, unitNo: string | undefined, roomNo: string) => {
|
||||
@@ -159,6 +161,107 @@ const AddShopDealerApply = () => {
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
type DupKey = 'address' | 'buildingNo' | 'unitNo' | 'roomNo' | 'realName' | 'mobile';
|
||||
const DUP_LABELS: Record<DupKey, string> = {
|
||||
address: '小区名称',
|
||||
buildingNo: '楼栋号',
|
||||
unitNo: '单元号',
|
||||
roomNo: '房号',
|
||||
realName: '姓名',
|
||||
mobile: '电话',
|
||||
};
|
||||
|
||||
const normalizeText = (v: any) => (v ?? '').toString().trim();
|
||||
|
||||
const getApplyDupFields = (apply: ShopDealerApply): Record<DupKey, string> => {
|
||||
const parsed = parseHouseKey(apply.dealerCode);
|
||||
return {
|
||||
address: normalizeText(parsed.community || apply.address),
|
||||
buildingNo: normalizeText(parsed.buildingNo),
|
||||
unitNo: normalizeText(parsed.unitNo),
|
||||
roomNo: normalizeText(parsed.roomNo),
|
||||
realName: normalizeText(apply.realName),
|
||||
mobile: normalizeText(apply.mobile),
|
||||
};
|
||||
};
|
||||
|
||||
const getNewDupFields = (values: any): Record<DupKey, string> => ({
|
||||
address: normalizeText(values.address),
|
||||
buildingNo: normalizeText(values.buildingNo),
|
||||
unitNo: normalizeText(values.unitNo),
|
||||
roomNo: normalizeText(values.roomNo),
|
||||
realName: normalizeText(values.realName),
|
||||
mobile: normalizeText(values.mobile),
|
||||
});
|
||||
|
||||
const combinationsOf3 = <T,>(arr: T[]): [T, T, T][] => {
|
||||
const res: [T, T, T][] = [];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
for (let j = i + 1; j < arr.length; j++) {
|
||||
for (let k = j + 1; k < arr.length; k++) {
|
||||
res.push([arr[i], arr[j], arr[k]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
const findMatchedTriad = (a: Record<DupKey, string>, b: Record<DupKey, string>) => {
|
||||
const availableKeys = (Object.keys(a) as DupKey[]).filter((k) => a[k] !== '');
|
||||
if (availableKeys.length < 3) return null;
|
||||
const triads = combinationsOf3(availableKeys);
|
||||
for (const triad of triads) {
|
||||
if (triad.every((k) => a[k] === b[k] && b[k] !== '')) return triad;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const checkDuplicateBeforeSubmit = async (values: any, opts?: { skipDealerCode?: string }) => {
|
||||
const inputFields = getNewDupFields(values);
|
||||
const nonEmptyCount = Object.values(inputFields).filter((v) => v !== '').length;
|
||||
if (nonEmptyCount < 3) return null;
|
||||
|
||||
const seen = new Set<number>();
|
||||
const scanPages = async (params: any) => {
|
||||
for (let page = 1; page <= DUP_CHECK_MAX_PAGES; page++) {
|
||||
const res = await pageShopDealerApply({...params, page, limit: DUP_CHECK_LIMIT});
|
||||
const list = res?.list || [];
|
||||
|
||||
for (const item of list) {
|
||||
if (opts?.skipDealerCode && item.dealerCode === opts.skipDealerCode) continue;
|
||||
if (item.applyId && seen.has(item.applyId)) continue;
|
||||
if (item.applyId) seen.add(item.applyId);
|
||||
|
||||
const triad = findMatchedTriad(inputFields, getApplyDupFields(item));
|
||||
if (triad) return {item, triad};
|
||||
}
|
||||
|
||||
if (list.length < DUP_CHECK_LIMIT) break;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// 优先按手机号(精确)查询,数据量更小
|
||||
if (inputFields.mobile) {
|
||||
const hit = await scanPages({type: 4, mobile: inputFields.mobile});
|
||||
if (hit) return hit;
|
||||
}
|
||||
|
||||
// 再按小区关键字查询,覆盖房号相关组合
|
||||
if (inputFields.address) {
|
||||
const hit = await scanPages({type: 4, keywords: inputFields.address});
|
||||
if (hit) return hit;
|
||||
}
|
||||
|
||||
// 最后按姓名关键字兜底(用于覆盖不包含“小区/电话”的组合)
|
||||
if (inputFields.realName) {
|
||||
const hit = await scanPages({type: 4, keywords: inputFields.realName});
|
||||
if (hit) return hit;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const submitSucceed = async (values: any) => {
|
||||
|
||||
try {
|
||||
@@ -272,6 +375,20 @@ const AddShopDealerApply = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 新增报备:提交前做“三要素”重复校验(小区/楼栋/单元/房号/姓名/电话 任一三要素重复提示已报备)
|
||||
if (!isEditMode) {
|
||||
const dup = await checkDuplicateBeforeSubmit(values, {skipDealerCode: houseKey});
|
||||
if (dup) {
|
||||
const triadLabels = dup.triad.map((k: DupKey) => DUP_LABELS[k]).join('、');
|
||||
const existingDisplay = dup.item.dealerName || dup.item.address || '地址未知';
|
||||
Taro.showToast({
|
||||
title: `疑似重复报备:${triadLabels}一致(${existingDisplay}),已报备`,
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 计算过期时间
|
||||
const expirationTime = isEditMode ? existingApply?.expirationTime : calculateExpirationTime();
|
||||
|
||||
Reference in New Issue
Block a user