import {useEffect, useState, useRef} from "react"; import {Loading, CellGroup, Cell, Input, Form, Calendar} from '@nutui/nutui-react-taro' import {Edit, Calendar as CalendarIcon} from '@nutui/icons-react-taro' import Taro from '@tarojs/taro' import {useRouter} from '@tarojs/taro' import {View, Text} from '@tarojs/components' import FixedButton from "@/components/FixedButton"; import {ShopDealerApply} from "@/api/shop/shopDealerApply/model"; import { addShopDealerApply, getShopDealerApply, pageShopDealerApply, updateShopDealerApply } from "@/api/shop/shopDealerApply"; import { formatDateForDatabase, extractDateForCalendar, formatDateForDisplay } from "@/utils/dateUtils"; import {ShopDealerUser} from "@/api/shop/shopDealerUser/model"; import {getShopDealerUser, pageShopDealerUser} from "@/api/shop/shopDealerUser"; const AddShopDealerApply = () => { const {params} = useRouter(); const [loading, setLoading] = useState(true) const [FormData, setFormData] = useState() const formRef = useRef(null) const [isEditMode, setIsEditMode] = useState(false) const [existingApply, setExistingApply] = useState(null) const [referee, setReferee] = useState() 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) => { const c = (community || '').trim(); const b = (buildingNo || '').trim(); const u = (unitNo || '').trim(); const r = (roomNo || '').trim(); return [c, b, u, r].join('|'); }; const buildHouseDisplay = (community: string, buildingNo: string, unitNo: string | undefined, roomNo: string) => { const c = (community || '').trim(); const b = (buildingNo || '').trim(); const u = (unitNo || '').trim(); const r = (roomNo || '').trim(); return `${c}${b ? `${b}栋` : ''}${u ? `${u}单元` : ''}${r ? `${r}号` : ''}`; }; const parseHouseKey = (key?: string) => { const parts = (key || '').split('|'); return { community: parts[0] || '', buildingNo: parts[1] || '', unitNo: parts[2] || '', roomNo: parts[3] || '', }; }; // 日期选择器状态 const [showApplyTimePicker, setShowApplyTimePicker] = useState(false) const [showContractTimePicker, setShowContractTimePicker] = useState(false) const [applyTime, setApplyTime] = useState('') const [contractTime, setContractTime] = useState('') // 获取审核状态文字 const getApplyStatusText = (status?: number) => { switch (status) { case 10: return '待审核' case 20: return '已签约' case 30: return '已取消' default: return '未知状态' } } console.log(getApplyStatusText) // 处理签约时间选择 const handleApplyTimeConfirm = (param: string) => { const selectedDate = param[3] // 选中的日期字符串 (YYYY-M-D) const formattedDate = formatDateForDatabase(selectedDate) // 转换为数据库格式 setApplyTime(selectedDate) // 保存原始格式用于显示 setShowApplyTimePicker(false) // 更新表单数据(使用数据库格式) if (formRef.current) { formRef.current.setFieldsValue({ applyTime: formattedDate }) } } // 处理合同日期选择 const handleContractTimeConfirm = (param: string) => { const selectedDate = param[3] // 选中的日期字符串 (YYYY-M-D) const formattedDate = formatDateForDatabase(selectedDate) // 转换为数据库格式 setContractTime(selectedDate) // 保存原始格式用于显示 setShowContractTimePicker(false) // 更新表单数据(使用数据库格式) if (formRef.current) { formRef.current.setFieldsValue({ contractTime: formattedDate }) } } const reload = async () => { // 查询推荐人信息 const dealerUser = await getShopDealerUser(Number(Taro.getStorageSync('UserId'))) setReferee(dealerUser) if (!params.id) { setLoading(false); return false; } // 查询当前用户ID是否已有申请记录 try { const dealerApply = await getShopDealerApply(Number(params.id)); if (dealerApply) { setFormData(dealerApply) setIsEditMode(true); setExistingApply(dealerApply) // 初始化日期数据(从数据库格式转换为Calendar组件格式) if (dealerApply.applyTime) { setApplyTime(extractDateForCalendar(dealerApply.applyTime)) } if (dealerApply.contractTime) { setContractTime(extractDateForCalendar(dealerApply.contractTime)) } Taro.setNavigationBarTitle({title: '签约'}) } } catch (error) { setLoading(false) console.error('查询申请记录失败:', error); setIsEditMode(false); setExistingApply(null); } } // 提交表单 // 计算保护期过期时间(15天后) const calculateExpirationTime = (): string => { const now = new Date(); const expirationDate = new Date(now); expirationDate.setDate(now.getDate() + PROTECTION_DAYS); // 15天后 // 格式化为数据库需要的格式:YYYY-MM-DD HH:mm:ss const year = expirationDate.getFullYear(); const month = String(expirationDate.getMonth() + 1).padStart(2, '0'); const day = String(expirationDate.getDate()).padStart(2, '0'); const hours = String(expirationDate.getHours()).padStart(2, '0'); const minutes = String(expirationDate.getMinutes()).padStart(2, '0'); const seconds = String(expirationDate.getSeconds()).padStart(2, '0'); 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 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 => ({ 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; }; // 优先按手机号(精确)查询,数据量更小 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 { // 房号相关必填校验 if (!values.address || values.address.trim() === '') { Taro.showToast({title: '请选择/填写小区', icon: 'error'}); return; } if (!values.buildingNo || values.buildingNo.trim() === '') { Taro.showToast({title: '请填写楼栋号', icon: 'error'}); return; } if (!values.roomNo || values.roomNo.trim() === '') { Taro.showToast({title: '请填写房号', icon: 'error'}); return; } if (!values.realName || values.realName.trim() === '') { Taro.showToast({title: '请填写姓名', icon: 'error'}); return; } // 验证必填字段 if (!values.mobile || values.mobile.trim() === '') { Taro.showToast({ title: '请填写联系方式', icon: 'error' }); return; } // 验证手机号格式 const phoneRegex = /^1[3-9]\d{9}$/; if (!phoneRegex.test(values.mobile)) { Taro.showToast({ title: '请填写正确的手机号', icon: 'error' }); return; } // 验证报备人是否存在 if (values.userId > 0) { const isExist = await pageShopDealerUser({userId: Number(values.userId)}); if(isExist && isExist.count == 0){ Taro.showToast({ title: '报备人不存在', icon: 'error' }); return; } } const houseKey = buildHouseKey(values.address, values.buildingNo, values.unitNo, values.roomNo); const houseDisplay = buildHouseDisplay(values.address, values.buildingNo, values.unitNo, values.roomNo); // 检查房号是否已报备 const res = await pageShopDealerApply({dealerCode: houseKey, type: 4}); if (res && res.count > 0) { const existingCustomer = res.list[0]; if (!isEditMode) { // 已签约/已取消:直接提示已报备 if (existingCustomer.applyStatus && existingCustomer.applyStatus !== 10) { Taro.showToast({ title: `该房号信息已报备(${getApplyStatusText(existingCustomer.applyStatus)}),本次报备未生效`, icon: 'none', duration: 2500 }); return false; } // 跟进中:保护期逻辑 if (existingCustomer.applyTime) { const applyTimeStamp = new Date(existingCustomer.applyTime).getTime(); const currentTimeStamp = new Date().getTime(); const protectionMs = PROTECTION_DAYS * 24 * 60 * 60 * 1000; if (currentTimeStamp - applyTimeStamp < protectionMs) { const remainingDays = Math.ceil((protectionMs - (currentTimeStamp - applyTimeStamp)) / (24 * 60 * 60 * 1000)); Taro.showToast({ title: `该房号信息已报备(${getApplyStatusText(existingCustomer.applyStatus)}),保护期剩余${remainingDays}天,本次报备未生效`, icon: 'none', duration: 3000 }); return false; } // 超过保护期:询问是否重新报备 const modalResult = await new Promise((resolve) => { Taro.showModal({ title: '提示', content: `该房号已超过${PROTECTION_DAYS}天保护期,是否重新报备跟进?`, showCancel: true, cancelText: '取消', confirmText: '确定', success: (modalRes) => resolve(modalRes.confirm), fail: () => resolve(false) }); }); if (!modalResult) return false; } else { Taro.showToast({ title: `该房号信息已报备(${getApplyStatusText(existingCustomer.applyStatus)}),本次报备未生效`, icon: 'none', duration: 2500 }); return false; } } } // 新增报备:提交前做“三要素”重复校验(小区/楼栋/单元/房号/姓名/电话 任一三要素重复提示已报备) 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(); // 准备提交的数据 // 避免把表单里的楼栋/单元/房号等临时字段原样提交给后端 const {buildingNo, unitNo, roomNo, ...restValues} = values; const submitData = { ...restValues, type: 4, // 展示用:小区+楼栋+单元+房号 dealerName: houseDisplay, // 唯一键:用于后续重复报备提示 dealerCode: houseKey, // 客户姓名/手机号 realName: values.realName, mobile: values.mobile, refereeId: referee?.refereeId, applyStatus: isEditMode ? 20 : 10, auditTime: undefined, // 设置保护期过期时间(15天后) expirationTime: expirationTime, // 确保日期数据正确提交(使用数据库格式) applyTime: values.applyTime || (applyTime ? formatDateForDatabase(applyTime) : ''), contractTime: values.contractTime || (contractTime ? formatDateForDatabase(contractTime) : '') }; // 调试信息 console.log('=== 提交数据调试 ==='); console.log('是否编辑模式:', isEditMode); console.log('计算的过期时间:', expirationTime); console.log('提交的数据:', submitData); console.log('=================='); // 如果是编辑模式,添加现有申请的id if (isEditMode && existingApply?.applyId) { submitData.applyId = existingApply.applyId; } // 执行新增或更新操作 if (isEditMode) { await updateShopDealerApply(submitData); } else { await addShopDealerApply(submitData); } Taro.showToast({ title: `${isEditMode ? '更新' : '提交'}成功`, icon: 'success' }); setTimeout(() => { Taro.navigateBack(); }, 1000); } catch (error) { console.error('提交失败:', error); Taro.showToast({ title: '提交失败,请重试', icon: 'error' }); } } // 处理固定按钮点击事件 const handleFixedButtonClick = () => { // 触发表单提交 formRef.current?.submit(); }; const submitFailed = (error: any) => { console.log(error, 'err...') } useEffect(() => { reload().then(() => { setLoading(false) }).catch((error) => { console.error('页面加载失败:', error); setLoading(false); // Taro.showToast({ // title: '页面加载失败', // icon: 'error' // }); }) }, []); // 依赖用户ID,当用户变化时重新加载 // 编辑模式下,从 dealerCode 反解出楼栋/单元/房号,回填表单(只读展示) useEffect(() => { if (!formRef.current || !FormData) return; const parsed = parseHouseKey(FormData.dealerCode); formRef.current.setFieldsValue({ address: parsed.community || FormData.address, buildingNo: parsed.buildingNo, unitNo: parsed.unitNo, roomNo: parsed.roomNo, realName: FormData.realName, mobile: FormData.mobile, userId: FormData.userId }); }, [FormData]); if (loading) { return 加载中 } return ( <>
submitSucceed(values)} onFinishFailed={(errors) => submitFailed(errors)} > {isEditMode && ( <> setShowApplyTimePicker(true)} > {applyTime ? formatDateForDisplay(applyTime) : '请选择签约时间'} setShowContractTimePicker(true)} > {contractTime ? formatDateForDisplay(contractTime) : '请选择合同生效起止时间'} {/**/} {/* */} {/**/} )}
{/* 签约时间选择器 */} setShowApplyTimePicker(false)} onConfirm={handleApplyTimeConfirm} /> {/* 合同日期选择器 */} setShowContractTimePicker(false)} onConfirm={handleContractTimeConfirm} /> {/* 审核状态显示(仅在编辑模式下显示) */} {isEditMode && ( {/**/} {/* {getApplyStatusText(FormData?.applyStatus)}*/} {/* */} {/* }*/} {/*/>*/} {FormData?.applyStatus === 20 && ( )} {FormData?.applyStatus === 30 && ( )} )} {/* 底部浮动按钮 */} {(!isEditMode || FormData?.applyStatus === 10) && ( } text={'立即提交'} onClick={handleFixedButtonClick} /> )} ); }; export default AddShopDealerApply;