diff --git a/src/dealer/customer/add.tsx b/src/dealer/customer/add.tsx index 636e891..6711f46 100644 --- a/src/dealer/customer/add.tsx +++ b/src/dealer/customer/add.tsx @@ -161,102 +161,123 @@ const AddShopDealerApply = () => { return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; }; - type DupKey = 'address' | 'buildingNo' | 'unitNo' | 'roomNo' | 'realName' | 'mobile'; - const DUP_LABELS: Record = { - address: '小区名称', - buildingNo: '楼栋号', - unitNo: '单元号', - roomNo: '房号', - realName: '姓名', - mobile: '电话', - }; - const normalizeText = (v: any) => (v ?? '').toString().trim(); - const getApplyDupFields = (apply: ShopDealerApply): Record => { + const toHalfWidth = (input: string) => + (input || '').replace(/[\uFF01-\uFF5E]/g, (ch) => String.fromCharCode(ch.charCodeAt(0) - 0xFEE0)).replace(/\u3000/g, ' '); + + const parseChineseNumber = (s: string): number | null => { + const str = (s || '').trim(); + if (!str) return null; + // 仅处理纯中文数字(含大小写)+ 单位 + if (!/^[零〇一二三四五六七八九十百千万两兩俩壹贰叁肆伍陆柒捌玖拾佰仟萬]+$/.test(str)) return null; + + const digitMap: Record = { + 零: 0, 〇: 0, + 一: 1, 壹: 1, + 二: 2, 两: 2, 兩: 2, 俩: 2, 贰: 2, + 三: 3, 叁: 3, + 四: 4, 肆: 4, + 五: 5, 伍: 5, + 六: 6, 陆: 6, + 七: 7, 柒: 7, + 八: 8, 捌: 8, + 九: 9, 玖: 9, + }; + const unitMap: Record = {十: 10, 拾: 10, 百: 100, 佰: 100, 千: 1000, 仟: 1000, 万: 10000, 萬: 10000}; + + let total = 0; + let section = 0; + let number = 0; + for (const ch of str) { + if (digitMap[ch] !== undefined) { + number = digitMap[ch]; + continue; + } + const unit = unitMap[ch]; + if (!unit) continue; + if (unit === 10000) { + section = (section + number) * unit; + total += section; + section = 0; + } else { + // “十/百/千”前省略“一”的情况:十=10、十二=12 + const n = number === 0 ? 1 : number; + section += n * unit; + } + number = 0; + } + const result = total + section + number; + return Number.isFinite(result) ? result : null; + }; + + const normalizeCommunity = (community: string) => { + const s = toHalfWidth(normalizeText(community)); + return s.replace(/\s+/g, '').toUpperCase(); + }; + + const normalizeHouseNoPart = (raw: string, kind: 'building' | 'unit' | 'room') => { + let s = toHalfWidth(normalizeText(raw)).toUpperCase(); + s = s.replace(/\s+/g, ''); + + // 去掉常见后缀/装饰词 + if (kind === 'building') s = s.replace(/(号楼|栋|幢|楼)$/g, ''); + if (kind === 'unit') s = s.replace(/(单元)$/g, ''); + if (kind === 'room') s = s.replace(/(室|房|号)$/g, ''); + + // 只保留数字与字母,统一分隔符差异(如 12-01 / 12#01) + s = s.replace(/[^0-9A-Z零〇一二三四五六七八九十百千万两兩俩壹贰叁肆伍陆柒捌玖拾佰仟萬]/g, ''); + + // 纯中文数字 => 阿拉伯数字(支持大小写) + const cn = parseChineseNumber(s); + if (cn !== null) return String(cn); + + // 数字段去前导 0(如 03A => 3A,1201 不变) + s = s.replace(/\d+/g, (m) => String(parseInt(m, 10))); + return s; + }; + + const buildHouseKeyNormalized = (community: string, buildingNo: string, unitNo: string | undefined, roomNo: string) => { + const c = normalizeCommunity(community); + const b = normalizeHouseNoPart(buildingNo, 'building'); + const u = normalizeHouseNoPart(unitNo || '', 'unit'); + const r = normalizeHouseNoPart(roomNo, 'room'); + return [c, b, u, r].join('|'); + }; + + const getNormalizedHouseKeyFromApply = (apply: ShopDealerApply) => { 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), - }; + return buildHouseKeyNormalized( + parsed.community || apply.address || '', + parsed.buildingNo || '', + parsed.unitNo || '', + parsed.roomNo || '' + ); }; - const getNewDupFields = (values: any): Record => ({ - 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 = (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, b: Record) => { - 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(); - 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; + const findExistingApplyByHouse = async (params: {houseKeyNormalized: string; houseKeyRaw: string; communityKeyword: string}) => { + const tryByDealerCode = async (dealerCode: string) => { + const res = await pageShopDealerApply({dealerCode, type: 4}); + return res?.list?.[0] as ShopDealerApply | undefined; }; - // 优先按手机号(精确)查询,数据量更小 - if (inputFields.mobile) { - const hit = await scanPages({type: 4, mobile: inputFields.mobile}); + const keys = Array.from(new Set([params.houseKeyNormalized, params.houseKeyRaw].filter(Boolean))); + for (const k of keys) { + const hit = await tryByDealerCode(k); if (hit) return hit; } - // 再按小区关键字查询,覆盖房号相关组合 - if (inputFields.address) { - const hit = await scanPages({type: 4, keywords: inputFields.address}); - if (hit) return hit; - } + // 兼容历史数据:用关键词拉取附近数据,再用“规范化后的 houseKey”对比 + const keyword = normalizeText(params.communityKeyword); + if (!keyword) return null; - // 最后按姓名关键字兜底(用于覆盖不包含“小区/电话”的组合) - if (inputFields.realName) { - const hit = await scanPages({type: 4, keywords: inputFields.realName}); - if (hit) return hit; + for (let page = 1; page <= DUP_CHECK_MAX_PAGES; page++) { + const res = await pageShopDealerApply({type: 4, keywords: keyword, page, limit: DUP_CHECK_LIMIT}); + const list = res?.list || []; + for (const item of list) { + if (getNormalizedHouseKeyFromApply(item) === params.houseKeyNormalized) return item; + } + if (list.length < DUP_CHECK_LIMIT) break; } return null; @@ -314,16 +335,20 @@ const AddShopDealerApply = () => { } } - const houseKey = buildHouseKey(values.address, values.buildingNo, values.unitNo, values.roomNo); + const houseKeyRaw = buildHouseKey(values.address, values.buildingNo, values.unitNo, values.roomNo); + const houseKeyNormalized = buildHouseKeyNormalized(values.address, values.buildingNo, values.unitNo, values.roomNo); + const houseKey = houseKeyNormalized || houseKeyRaw; const houseDisplay = buildHouseDisplay(values.address, values.buildingNo, values.unitNo, values.roomNo); - // 检查房号是否已报备 - const res = await pageShopDealerApply({dealerCode: houseKey, type: 4}); + // 新增报备:提交前检查房号是否已报备(按 小区+楼栋+单元+房号 判断,且做规范化) + if (!isEditMode) { + const existingCustomer = await findExistingApplyByHouse({ + houseKeyNormalized, + houseKeyRaw, + communityKeyword: values.address + }); - if (res && res.count > 0) { - const existingCustomer = res.list[0]; - - if (!isEditMode) { + if (existingCustomer) { // 已签约/已取消:直接提示已报备 if (existingCustomer.applyStatus && existingCustomer.applyStatus !== 10) { Taro.showToast({ @@ -375,21 +400,6 @@ 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();