Files
template-10582/src/dealer/customer/add-bak.tsx
赵忠林 1c7f35b40f fix(dealer): 移除楼层字段优化房号相关逻辑及表单
- 删除楼层相关代码及状态管理
- 更新房号唯一键及展示逻辑,去除楼层字段
- 表单中楼层输入改为普通输入框,禁用编辑状态
- 修正提交及校验逻辑,统一房号字段处理
- 简化编辑模式房号数据回填过程
- 移除小区、楼栋、单元、楼层、房号弹出选择组件及相关逻辑
- 更改提示文案,从“请选择”改为“请填写”房号相关项
2026-04-24 20:26:52 +08:00

1550 lines
53 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {useEffect, useState, useRef} from "react";
import {Loading, CellGroup, Cell, Input, Form, Calendar, Popup, SearchBar} from '@nutui/nutui-react-taro'
import {Edit, Calendar as CalendarIcon, ArrowRight, Del} 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";
import {listDictData} from "@/api/system/dict-data";
import type {DictData} from "@/api/system/dict-data/model";
const AddShopDealerApply = () => {
const {params} = useRouter();
const [loading, setLoading] = useState<boolean>(true)
const [FormData, setFormData] = useState<ShopDealerApply>()
const formRef = useRef<any>(null)
const [isEditMode, setIsEditMode] = useState<boolean>(false)
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, floorNo: string | undefined, roomNo: string) => {
const c = (community || '').trim();
const b = (buildingNo || '').trim();
const u = (unitNo || '').trim();
const f = (floorNo || '').trim();
const r = (roomNo || '').trim();
return [c, b, u, f, r].join('|');
};
const buildHouseDisplay = (community: string, buildingNo: string, unitNo: string | undefined, floorNo: string | undefined, roomNo: string) => {
const c = (community || '').trim();
const b = (buildingNo || '').trim();
const u = (unitNo || '').trim();
const f = (floorNo || '').trim();
const r = (roomNo || '').trim();
return `${c}${b ? `${b}` : ''}${u ? `${u}单元` : ''}${f ? `${f}` : ''}${r ? `${r}` : ''}`;
};
const parseHouseKey = (key?: string) => {
const parts = (key || '').split('|');
return {
community: parts[0] || '',
buildingNo: parts[1] || '',
unitNo: parts[2] || '',
floorNo: parts[3] || '',
roomNo: parts[4] || '',
};
};
// 日期选择器状态
const [showApplyTimePicker, setShowApplyTimePicker] = useState<boolean>(false)
const [showContractTimePicker, setShowContractTimePicker] = useState<boolean>(false)
const [applyTime, setApplyTime] = useState<string>('')
const [contractTime, setContractTime] = useState<string>('')
// 接待人员选择状态
const [showReceptionistPicker, setShowReceptionistPicker] = useState<boolean>(false)
const [receptionistSearch, setReceptionistSearch] = useState<string>('')
const [receptionistList, setReceptionistList] = useState<ShopDealerUser[]>([])
const [receptionistLoading, setReceptionistLoading] = useState<boolean>(false)
const [selectedReceptionist, setSelectedReceptionist] = useState<ShopDealerUser | null>(null)
// 小区选择状态
const [showCommunityPicker, setShowCommunityPicker] = useState<boolean>(false)
const [communitySearch, setCommunitySearch] = useState<string>('')
const [communityList, setCommunityList] = useState<DictData[]>([])
const [communityLoading, setCommunityLoading] = useState<boolean>(false)
const [selectedCommunity, setSelectedCommunity] = useState<DictData | null>(null)
// 楼栋选择状态
const [showBuildingPicker, setShowBuildingPicker] = useState<boolean>(false)
const [buildingSearch, setBuildingSearch] = useState<string>('')
const [buildingList, setBuildingList] = useState<DictData[]>([])
const [buildingLoading, setBuildingLoading] = useState<boolean>(false)
const [selectedBuilding, setSelectedBuilding] = useState<DictData | null>(null)
// 单元选择状态
const [showUnitPicker, setShowUnitPicker] = useState<boolean>(false)
const [unitSearch, setUnitSearch] = useState<string>('')
const [unitList, setUnitList] = useState<DictData[]>([])
const [unitLoading, setUnitLoading] = useState<boolean>(false)
const [selectedUnit, setSelectedUnit] = useState<DictData | null>(null)
// 楼层选择状态
const [showFloorPicker, setShowFloorPicker] = useState<boolean>(false)
const [floorSearch, setFloorSearch] = useState<string>('')
const [floorList, setFloorList] = useState<DictData[]>([])
const [floorLoading, setFloorLoading] = useState<boolean>(false)
const [selectedFloor, setSelectedFloor] = useState<DictData | null>(null)
// 房号选择状态
const [showRoomPicker, setShowRoomPicker] = useState<boolean>(false)
const [roomSearch, setRoomSearch] = useState<string>('')
const [roomList, setRoomList] = useState<DictData[]>([])
const [roomLoading, setRoomLoading] = useState<boolean>(false)
const [selectedRoom, setSelectedRoom] = useState<DictData | null>(null)
// 获取审核状态文字
const getApplyStatusText = (status?: number) => {
switch (status) {
case 10:
return '待审核'
case 20:
return '已签约'
case 30:
return '已取消'
default:
return '未知状态'
}
}
// 处理签约时间选择
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))
}
// 回填接待人员
if (dealerApply.receptionistId) {
setSelectedReceptionist({
userId: dealerApply.receptionistId,
dealerName: dealerApply.receptionistName || '',
realName: dealerApply.receptionistName || '',
})
}
Taro.setNavigationBarTitle({title: '签约'})
}
} catch (error) {
setLoading(false)
console.error('查询申请记录失败:', error);
setIsEditMode(false);
setExistingApply(null);
}
}
// 加载接待人员列表
const loadReceptionistList = async (keyword?: string) => {
setReceptionistLoading(true)
try {
const res = await pageShopDealerUser({keywords: keyword || '', limit: 50, page: 1})
setReceptionistList(res?.list || [])
} catch (e) {
console.error('加载接待人员失败:', e)
} finally {
setReceptionistLoading(false)
}
}
// 打开接待人员选择
const openReceptionistPicker = () => {
setReceptionistSearch('')
loadReceptionistList()
setShowReceptionistPicker(true)
}
// 搜索接待人员
const handleReceptionistSearch = (val: string) => {
setReceptionistSearch(val)
loadReceptionistList(val)
}
// 选择接待人员
const handleSelectReceptionist = (user: ShopDealerUser) => {
setSelectedReceptionist(user)
setShowReceptionistPicker(false)
}
// 清除接待人员
const handleClearReceptionist = () => {
setSelectedReceptionist(null)
}
// 加载小区列表
const loadCommunityList = async (keyword?: string) => {
setCommunityLoading(true)
try {
const list = await listDictData({ dictCode: 'xiaoqu' })
// 过滤搜索关键词
if (keyword) {
setCommunityList(list.filter((item: DictData) =>
(item.dictDataName || '').includes(keyword) ||
(item.label || '').includes(keyword)
))
} else {
setCommunityList(list)
}
} catch (e) {
console.error('加载小区列表失败:', e)
} finally {
setCommunityLoading(false)
}
}
// 打开小区选择
const openCommunityPicker = () => {
setCommunitySearch('')
loadCommunityList()
setShowCommunityPicker(true)
}
// 搜索小区
const handleCommunitySearch = (val: string) => {
setCommunitySearch(val)
loadCommunityList(val)
}
// 选择小区
const handleSelectCommunity = (item: DictData) => {
setSelectedCommunity(item)
setShowCommunityPicker(false)
// 更新表单数据
if (formRef.current) {
formRef.current.setFieldsValue({
address: item.dictDataName || item.label || ''
})
}
}
// 清除小区
const handleClearCommunity = () => {
setSelectedCommunity(null)
if (formRef.current) {
formRef.current.setFieldsValue({
address: ''
})
}
}
// 加载楼栋列表
const loadBuildingList = async (keyword?: string) => {
setBuildingLoading(true)
try {
const list = await listDictData({ dictCode: 'building' })
if (keyword) {
setBuildingList(list.filter((item: DictData) =>
(item.dictDataName || '').includes(keyword) ||
(item.label || '').includes(keyword)
))
} else {
setBuildingList(list)
}
} catch (e) {
console.error('加载楼栋列表失败:', e)
} finally {
setBuildingLoading(false)
}
}
// 打开楼栋选择
const openBuildingPicker = () => {
setBuildingSearch('')
loadBuildingList()
setShowBuildingPicker(true)
}
// 搜索楼栋
const handleBuildingSearch = (val: string) => {
setBuildingSearch(val)
loadBuildingList(val)
}
// 选择楼栋
const handleSelectBuilding = (item: DictData) => {
setSelectedBuilding(item)
setShowBuildingPicker(false)
if (formRef.current) {
formRef.current.setFieldsValue({
buildingNo: item.dictDataName || item.label || ''
})
}
}
// 清除楼栋
const handleClearBuilding = () => {
setSelectedBuilding(null)
if (formRef.current) {
formRef.current.setFieldsValue({
buildingNo: ''
})
}
}
// 加载单元列表
const loadUnitList = async (keyword?: string) => {
setUnitLoading(true)
try {
const list = await listDictData({ dictCode: 'unit' })
if (keyword) {
setUnitList(list.filter((item: DictData) =>
(item.dictDataName || '').includes(keyword) ||
(item.label || '').includes(keyword)
))
} else {
setUnitList(list)
}
} catch (e) {
console.error('加载单元列表失败:', e)
} finally {
setUnitLoading(false)
}
}
// 打开单元选择
const openUnitPicker = () => {
setUnitSearch('')
loadUnitList()
setShowUnitPicker(true)
}
// 搜索单元
const handleUnitSearch = (val: string) => {
setUnitSearch(val)
loadUnitList(val)
}
// 选择单元
const handleSelectUnit = (item: DictData) => {
setSelectedUnit(item)
setShowUnitPicker(false)
if (formRef.current) {
formRef.current.setFieldsValue({
unitNo: item.dictDataName || item.label || ''
})
}
}
// 清除单元
const handleClearUnit = () => {
setSelectedUnit(null)
if (formRef.current) {
formRef.current.setFieldsValue({
unitNo: ''
})
}
}
// 加载楼层列表
const loadFloorList = async (keyword?: string) => {
setFloorLoading(true)
try {
const list = await listDictData({ dictCode: 'floor' })
if (keyword) {
setFloorList(list.filter((item: DictData) =>
(item.dictDataName || '').includes(keyword) ||
(item.label || '').includes(keyword)
))
} else {
setFloorList(list)
}
} catch (e) {
console.error('加载楼层列表失败:', e)
} finally {
setFloorLoading(false)
}
}
// 打开楼层选择
const openFloorPicker = () => {
setFloorSearch('')
loadFloorList()
setShowFloorPicker(true)
}
// 搜索楼层
const handleFloorSearch = (val: string) => {
setFloorSearch(val)
loadFloorList(val)
}
// 选择楼层
const handleSelectFloor = (item: DictData) => {
setSelectedFloor(item)
setShowFloorPicker(false)
if (formRef.current) {
formRef.current.setFieldsValue({
floorNo: item.dictDataName || item.label || ''
})
}
}
// 清除楼层
const handleClearFloor = () => {
setSelectedFloor(null)
if (formRef.current) {
formRef.current.setFieldsValue({
floorNo: ''
})
}
}
// 加载房号列表
const loadRoomList = async (keyword?: string) => {
setRoomLoading(true)
try {
const list = await listDictData({ dictCode: 'room' })
if (keyword) {
setRoomList(list.filter((item: DictData) =>
(item.dictDataName || '').includes(keyword) ||
(item.label || '').includes(keyword)
))
} else {
setRoomList(list)
}
} catch (e) {
console.error('加载房号列表失败:', e)
} finally {
setRoomLoading(false)
}
}
// 打开房号选择
const openRoomPicker = () => {
setRoomSearch('')
loadRoomList()
setShowRoomPicker(true)
}
// 搜索房号
const handleRoomSearch = (val: string) => {
setRoomSearch(val)
loadRoomList(val)
}
// 选择房号
const handleSelectRoom = (item: DictData) => {
setSelectedRoom(item)
setShowRoomPicker(false)
if (formRef.current) {
formRef.current.setFieldsValue({
roomNo: item.dictDataName || item.label || ''
})
}
}
// 清除房号
const handleClearRoom = () => {
setSelectedRoom(null)
if (formRef.current) {
formRef.current.setFieldsValue({
roomNo: ''
})
}
}
// 提交表单
// 计算保护期过期时间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}`;
};
const normalizeText = (v: any) => (v ?? '').toString().trim();
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<string, number> = {
: 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<string, number> = {: 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' | 'floor' | '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 === 'floor') 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 => 3A1201 不变)
s = s.replace(/\d+/g, (m) => String(parseInt(m, 10)));
return s;
};
const buildHouseKeyNormalized = (community: string, buildingNo: string, unitNo: string | undefined, floorNo: string | undefined, roomNo: string) => {
const c = normalizeCommunity(community);
const b = normalizeHouseNoPart(buildingNo, 'building');
const u = normalizeHouseNoPart(unitNo || '', 'unit');
const f = normalizeHouseNoPart(floorNo || '', 'floor');
const r = normalizeHouseNoPart(roomNo, 'room');
return [c, b, u, f, r].join('|');
};
const getNormalizedHouseKeyFromApply = (apply: ShopDealerApply) => {
const parsed = parseHouseKey(apply.dealerCode);
return buildHouseKeyNormalized(
parsed.community || apply.address || '',
parsed.buildingNo || '',
parsed.unitNo || '',
parsed.floorNo || '',
parsed.roomNo || ''
);
};
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;
};
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;
}
// 兼容历史数据:用关键词拉取附近数据,再用“规范化后的 houseKey”对比
const keyword = normalizeText(params.communityKeyword);
if (!keyword) return null;
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;
};
const submitSucceed = async (values: any) => {
try {
const currentUserId = Number(Taro.getStorageSync('UserId')) || 0;
// 房号相关必填校验
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.floorNo || values.floorNo.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;
}
// 规范化报备人:留空=自己报备(当前登录用户)
const rawUserId = normalizeText(values.userId);
const submitUserId = rawUserId
? Number(rawUserId)
: (isEditMode ? (existingApply?.userId || currentUserId) : currentUserId);
if (!Number.isFinite(submitUserId) || submitUserId <= 0) {
Taro.showToast({title: '请填写正确的报备人ID', icon: 'error'});
return;
}
// 报备人存在性校验 + 获取该报备人的推荐人(用于后端展示链路)
let reporterDealerUser: ShopDealerUser | undefined = undefined;
if (submitUserId === currentUserId) {
reporterDealerUser = referee;
} else {
try {
reporterDealerUser = await getShopDealerUser(submitUserId);
} catch {
Taro.showToast({title: '报备人不存在', icon: 'error'});
return;
}
}
// 后端常用 0 表示“无推荐人”,避免传空值触发“推荐人不存在”
const submitRefereeId = (reporterDealerUser?.refereeId && reporterDealerUser.refereeId > 0)
? reporterDealerUser.refereeId
: undefined;
const houseKeyRaw = buildHouseKey(values.address, values.buildingNo, values.unitNo, values.floorNo, values.roomNo);
const houseKeyNormalized = buildHouseKeyNormalized(values.address, values.buildingNo, values.unitNo, values.floorNo, values.roomNo);
const houseKey = houseKeyNormalized || houseKeyRaw;
const houseDisplay = buildHouseDisplay(values.address, values.buildingNo, values.unitNo, values.floorNo, values.roomNo);
// 新增报备:提交前检查房号是否已报备(按 小区+楼栋+单元+房号 判断,且做规范化)
if (!isEditMode) {
const existingCustomer = await findExistingApplyByHouse({
houseKeyNormalized,
houseKeyRaw,
communityKeyword: values.address
});
if (existingCustomer) {
// 报备人不同:直接拦截(避免跨报备人“抢单/续报”)
const existingReporterId = Number(existingCustomer.userId);
if (Number.isFinite(existingReporterId) && existingReporterId > 0 && existingReporterId !== submitUserId) {
Taro.showToast({
title: '请改房号,该房号信息已报备',
icon: 'none',
duration: 2500
});
return false;
}
// 已签约/已取消:直接提示已报备
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<boolean>((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;
}
}
}
// 计算过期时间
const expirationTime = isEditMode ? existingApply?.expirationTime : calculateExpirationTime();
// 准备提交的数据
// 避免把表单里的楼栋/单元/楼层/房号等临时字段原样提交给后端
const {buildingNo, unitNo, floorNo, roomNo, ...restValues} = values;
const submitData = {
...restValues,
type: 4,
// 展示用:小区+楼栋+单元+房号
dealerName: houseDisplay,
// 唯一键:用于后续重复报备提示
dealerCode: houseKey,
// 客户姓名/手机号
realName: values.realName,
mobile: values.mobile,
// 报备人(留空时用当前登录用户)
// userId: submitUserId,
// 推荐人(报备人的上级;无则传 0
refereeId: submitRefereeId,
applyStatus: isEditMode ? 20 : 10,
auditTime: undefined,
// 设置保护期过期时间15天后
expirationTime: expirationTime,
// 确保日期数据正确提交(使用数据库格式)
applyTime: values.applyTime || (applyTime ? formatDateForDatabase(applyTime) : ''),
contractTime: values.contractTime || (contractTime ? formatDateForDatabase(contractTime) : ''),
// 接待人员
receptionistId: selectedReceptionist?.userId || undefined,
receptionistName: selectedReceptionist ? (selectedReceptionist.realName || selectedReceptionist.dealerName || '') : undefined,
};
// 调试信息
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);
const communityValue = parsed.community || FormData.address || '';
formRef.current.setFieldsValue({
address: communityValue,
buildingNo: parsed.buildingNo,
unitNo: parsed.unitNo,
floorNo: parsed.floorNo,
roomNo: parsed.roomNo,
realName: FormData.realName,
mobile: FormData.mobile
});
// 回填小区选中状态
if (communityValue) {
setSelectedCommunity({
dictDataName: communityValue,
label: communityValue
} as DictData)
}
// 回填楼栋选中状态
if (parsed.buildingNo) {
setSelectedBuilding({
dictDataName: parsed.buildingNo,
label: parsed.buildingNo
} as DictData)
}
// 回填单元选中状态
if (parsed.unitNo) {
setSelectedUnit({
dictDataName: parsed.unitNo,
label: parsed.unitNo
} as DictData)
}
// 回填楼层选中状态
if (parsed.floorNo) {
setSelectedFloor({
dictDataName: parsed.floorNo,
label: parsed.floorNo
} as DictData)
}
// 回填房号选中状态
if (parsed.roomNo) {
setSelectedRoom({
dictDataName: parsed.roomNo,
label: parsed.roomNo
} as DictData)
}
}, [FormData]);
if (loading) {
return <Loading className={'px-2'}></Loading>
}
return (
<>
<Form
ref={formRef}
divider
initialValues={FormData}
labelPosition="left"
onFinish={(values) => submitSucceed(values)}
onFinishFailed={(errors) => submitFailed(errors)}
>
<View className={'bg-gray-100 h-3'}></View>
<CellGroup style={{padding: '4px 0'}}>
<Cell
title="小区"
required={true}
extra={
<View className="flex items-center">
{selectedCommunity ? (
<View className="flex items-center">
<Text className="text-sm text-gray-800 mr-2">
{selectedCommunity.dictDataName || selectedCommunity.label}
</Text>
{!isEditMode && (
<View
onClick={(e) => { e.stopPropagation(); handleClearCommunity(); }}
className="flex items-center px-1"
>
<Del size={14} color="#999"/>
</View>
)}
</View>
) : (
<Text className="text-sm text-gray-400"></Text>
)}
<ArrowRight size={14} color="#ccc"/>
</View>
}
onClick={isEditMode ? undefined : openCommunityPicker}
/>
{/* 隐藏字段,通过 ref.setFieldsValue 设置 */}
<Form.Item name="address" style={{display: 'none'}}>
<View />
</Form.Item>
{/* 楼栋选择 */}
<Cell
title="楼栋"
extra={
<View className="flex items-center">
{selectedBuilding ? (
<View className="flex items-center">
<Text className="text-sm text-gray-800 mr-2">{selectedBuilding.dictDataName || selectedBuilding.label}</Text>
{!isEditMode && (
<View
onClick={(e) => { e.stopPropagation(); handleClearBuilding(); }}
className="flex items-center px-1"
>
<Del size={14} color="#999"/>
</View>
)}
</View>
) : (
<Text className="text-sm text-gray-400"></Text>
)}
{!isEditMode && <ArrowRight size={14} color="#ccc"/>}
</View>
}
onClick={isEditMode ? undefined : openBuildingPicker}
/>
{/* 隐藏字段,通过 ref.setFieldsValue 设置 */}
<Form.Item name="buildingNo" style={{display: 'none'}}>
<View />
</Form.Item>
{/* 单元选择 */}
<Cell
title="单元"
extra={
<View className="flex items-center">
{selectedUnit ? (
<View className="flex items-center">
<Text className="text-sm text-gray-800 mr-2">{selectedUnit.dictDataName || selectedUnit.label}</Text>
{!isEditMode && (
<View
onClick={(e) => { e.stopPropagation(); handleClearUnit(); }}
className="flex items-center px-1"
>
<Del size={14} color="#999"/>
</View>
)}
</View>
) : (
<Text className="text-sm text-gray-400"></Text>
)}
{!isEditMode && <ArrowRight size={14} color="#ccc"/>}
</View>
}
onClick={isEditMode ? undefined : openUnitPicker}
/>
{/* 隐藏字段,通过 ref.setFieldsValue 设置 */}
<Form.Item name="unitNo" style={{display: 'none'}}>
<View />
</Form.Item>
{/* 楼层选择 */}
<Cell
title="楼层"
extra={
<View className="flex items-center">
{selectedFloor ? (
<View className="flex items-center">
<Text className="text-sm text-gray-800 mr-2">{selectedFloor.dictDataName || selectedFloor.label}</Text>
{!isEditMode && (
<View
onClick={(e) => { e.stopPropagation(); handleClearFloor(); }}
className="flex items-center px-1"
>
<Del size={14} color="#999"/>
</View>
)}
</View>
) : (
<Text className="text-sm text-gray-400"></Text>
)}
{!isEditMode && <ArrowRight size={14} color="#ccc"/>}
</View>
}
onClick={isEditMode ? undefined : openFloorPicker}
/>
{/* 隐藏字段,通过 ref.setFieldsValue 设置 */}
<Form.Item name="floorNo" style={{display: 'none'}}>
<View />
</Form.Item>
{/* 房号选择 */}
<Cell
title="房号"
required={true}
extra={
<View className="flex items-center">
{selectedRoom ? (
<View className="flex items-center">
<Text className="text-sm text-gray-800 mr-2">{selectedRoom.dictDataName || selectedRoom.label}</Text>
{!isEditMode && (
<View
onClick={(e) => { e.stopPropagation(); handleClearRoom(); }}
className="flex items-center px-1"
>
<Del size={14} color="#999"/>
</View>
)}
</View>
) : (
<Text className="text-sm text-gray-400"></Text>
)}
{!isEditMode && <ArrowRight size={14} color="#ccc"/>}
</View>
}
onClick={isEditMode ? undefined : openRoomPicker}
/>
{/* 隐藏字段,通过 ref.setFieldsValue 设置 */}
<Form.Item name="roomNo" style={{display: 'none'}}>
<View />
</Form.Item>
{/* 接待人员选择 */}
<Cell
title="接待人员"
extra={
<View className="flex items-center">
{selectedReceptionist ? (
<View className="flex items-center">
<Text className="text-sm text-gray-800 mr-2">
{selectedReceptionist.realName || selectedReceptionist.dealerName || '已选择'}
</Text>
<View
onClick={(e) => { e.stopPropagation(); handleClearReceptionist(); }}
className="flex items-center px-1"
>
<Del size={14} color="#999"/>
</View>
</View>
) : (
<Text className="text-sm text-gray-400"></Text>
)}
<ArrowRight size={14} color="#ccc"/>
</View>
}
onClick={openReceptionistPicker}
/>
<div className={'h-3 bg-gray-50'}></div>
<Form.Item name="realName" label="姓名" initialValue={FormData?.realName} required>
<Input placeholder="张三" disabled={isEditMode}/>
</Form.Item>
<Form.Item name="mobile" label="手机号" initialValue={FormData?.mobile} required>
<Input placeholder="手机号" disabled={isEditMode} maxLength={11}/>
</Form.Item>
<Form.Item name="comments" label="备注" initialValue={FormData?.comments}>
<Input placeholder="请输入备注信息" />
</Form.Item>
{isEditMode && (
<>
<Form.Item name="money" label="签约价格" initialValue={FormData?.money} required>
<Input placeholder="(元/兆瓦时)" />
</Form.Item>
<Form.Item name="applyTime" label="签约时间" initialValue={FormData?.applyTime}>
<View onClick={() => setShowApplyTimePicker(true)}>
<Input
placeholder="点击选择签约时间"
readOnly
value={applyTime ? formatDateForDisplay(applyTime) : ''}
/>
</View>
</Form.Item>
<Form.Item name="contractTime" label="合同日期" initialValue={FormData?.contractTime}>
<View onClick={() => setShowContractTimePicker(true)}>
<Input
placeholder="点击选择合同生效起止时间"
readOnly
value={contractTime ? formatDateForDisplay(contractTime) : ''}
/>
</View>
</Form.Item>
{/*<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>*/}
{/* <Input placeholder="邀请人ID"/>*/}
{/*</Form.Item>*/}
</>
)}
{/*<Form.Item name="userId" label="报备人(ID)" initialValue={FormData?.userId}>*/}
{/* <Input*/}
{/* placeholder="自己报备请留空"*/}
{/* disabled={isEditMode}*/}
{/* type="number"*/}
{/* />*/}
{/*</Form.Item>*/}
</CellGroup>
</Form>
{/* 签约时间选择器 */}
<Calendar
visible={showApplyTimePicker}
defaultValue={applyTime}
onClose={() => setShowApplyTimePicker(false)}
onConfirm={handleApplyTimeConfirm}
/>
{/* 合同日期选择器 */}
<Calendar
visible={showContractTimePicker}
defaultValue={contractTime}
onClose={() => setShowContractTimePicker(false)}
onConfirm={handleContractTimeConfirm}
/>
{/* 接待人员选择弹出层 */}
<Popup
visible={showReceptionistPicker}
position="bottom"
round
onClose={() => setShowReceptionistPicker(false)}
style={{height: '70%'}}
>
<View className="flex flex-col h-full">
{/* 标题栏 */}
<View className="flex items-center justify-between px-4 py-3 border-b border-gray-100">
<Text className="text-base font-semibold text-gray-800"></Text>
<View onClick={() => setShowReceptionistPicker(false)}>
<Text className="text-sm text-blue-500"></Text>
</View>
</View>
{/* 搜索框 */}
<View className="px-3 py-2">
<SearchBar
value={receptionistSearch}
placeholder="搜索姓名/手机号"
onChange={handleReceptionistSearch}
/>
</View>
{/* 列表 */}
<View className="flex-1 overflow-y-auto">
{receptionistLoading ? (
<View className="flex justify-center items-center py-8">
<Loading></Loading>
</View>
) : receptionistList.length === 0 ? (
<View className="flex justify-center items-center py-8">
<Text className="text-sm text-gray-400"></Text>
</View>
) : (
receptionistList.map((user) => (
<Cell
key={user.userId}
title={user.realName || user.dealerName || '未知'}
description={user.mobile || user.dealerPhone || ''}
extra={
selectedReceptionist?.userId === user.userId ? (
<Text className="text-sm text-blue-500"></Text>
) : null
}
onClick={() => handleSelectReceptionist(user)}
/>
))
)}
</View>
</View>
</Popup>
{/* 小区选择弹出层 */}
<Popup
visible={showCommunityPicker}
position="bottom"
round
onClose={() => setShowCommunityPicker(false)}
style={{height: '70%'}}
>
<View className="flex flex-col h-full">
{/* 标题栏 */}
<View className="flex items-center justify-between px-4 py-3 border-b border-gray-100">
<Text className="text-base font-semibold text-gray-800"></Text>
<View onClick={() => setShowCommunityPicker(false)}>
<Text className="text-sm text-blue-500"></Text>
</View>
</View>
{/* 搜索框 */}
<View className="px-3 py-2">
<SearchBar
value={communitySearch}
placeholder="搜索小区名称"
onChange={handleCommunitySearch}
/>
</View>
{/* 列表 */}
<View className="flex-1 overflow-y-auto">
{communityLoading ? (
<View className="flex justify-center items-center py-8">
<Loading></Loading>
</View>
) : communityList.length === 0 ? (
<View className="flex justify-center items-center py-8">
<Text className="text-sm text-gray-400"></Text>
</View>
) : (
communityList.map((item, index) => (
<Cell
key={item.dictDataId || index}
title={item.dictDataName || item.label || ''}
description={item.comments || ''}
extra={
selectedCommunity?.dictDataId === item.dictDataId ? (
<Text className="text-sm text-blue-500"></Text>
) : null
}
onClick={() => handleSelectCommunity(item)}
/>
))
)}
</View>
</View>
</Popup>
{/* 楼栋选择弹出层 */}
<Popup
visible={showBuildingPicker}
position="bottom"
round
onClose={() => setShowBuildingPicker(false)}
style={{height: '70%'}}
>
<View className="flex flex-col h-full">
{/* 标题栏 */}
<View className="flex items-center justify-between px-4 py-3 border-b border-gray-100">
<Text className="text-base font-semibold text-gray-800"></Text>
<View onClick={() => setShowBuildingPicker(false)}>
<Text className="text-sm text-blue-500"></Text>
</View>
</View>
{/* 搜索框 */}
<View className="px-3 py-2">
<SearchBar
value={buildingSearch}
placeholder="搜索楼栋号"
onChange={handleBuildingSearch}
/>
</View>
{/* 列表 */}
<View className="flex-1 overflow-y-auto">
{buildingLoading ? (
<View className="flex justify-center items-center py-8">
<Loading></Loading>
</View>
) : buildingList.length === 0 ? (
<View className="flex justify-center items-center py-8">
<Text className="text-sm text-gray-400"></Text>
</View>
) : (
buildingList.map((item, index) => (
<Cell
key={item.dictDataId || index}
title={item.dictDataName || item.label || ''}
extra={
selectedBuilding?.dictDataId === item.dictDataId ? (
<Text className="text-sm text-blue-500"></Text>
) : null
}
onClick={() => handleSelectBuilding(item)}
/>
))
)}
</View>
</View>
</Popup>
{/* 单元选择弹出层 */}
<Popup
visible={showUnitPicker}
position="bottom"
round
onClose={() => setShowUnitPicker(false)}
style={{height: '70%'}}
>
<View className="flex flex-col h-full">
{/* 标题栏 */}
<View className="flex items-center justify-between px-4 py-3 border-b border-gray-100">
<Text className="text-base font-semibold text-gray-800"></Text>
<View onClick={() => setShowUnitPicker(false)}>
<Text className="text-sm text-blue-500"></Text>
</View>
</View>
{/* 搜索框 */}
<View className="px-3 py-2">
<SearchBar
value={unitSearch}
placeholder="搜索单元号"
onChange={handleUnitSearch}
/>
</View>
{/* 列表 */}
<View className="flex-1 overflow-y-auto">
{unitLoading ? (
<View className="flex justify-center items-center py-8">
<Loading></Loading>
</View>
) : unitList.length === 0 ? (
<View className="flex justify-center items-center py-8">
<Text className="text-sm text-gray-400"></Text>
</View>
) : (
unitList.map((item, index) => (
<Cell
key={item.dictDataId || index}
title={item.dictDataName || item.label || ''}
extra={
selectedUnit?.dictDataId === item.dictDataId ? (
<Text className="text-sm text-blue-500"></Text>
) : null
}
onClick={() => handleSelectUnit(item)}
/>
))
)}
</View>
</View>
</Popup>
{/* 楼层选择弹出层 */}
<Popup
visible={showFloorPicker}
position="bottom"
round
onClose={() => setShowFloorPicker(false)}
style={{height: '70%'}}
>
<View className="flex flex-col h-full">
{/* 标题栏 */}
<View className="flex items-center justify-between px-4 py-3 border-b border-gray-100">
<Text className="text-base font-semibold text-gray-800"></Text>
<View onClick={() => setShowFloorPicker(false)}>
<Text className="text-sm text-blue-500"></Text>
</View>
</View>
{/* 搜索框 */}
<View className="px-3 py-2">
<SearchBar
value={floorSearch}
placeholder="搜索楼层"
onChange={handleFloorSearch}
/>
</View>
{/* 列表 */}
<View className="flex-1 overflow-y-auto">
{floorLoading ? (
<View className="flex justify-center items-center py-8">
<Loading></Loading>
</View>
) : floorList.length === 0 ? (
<View className="flex justify-center items-center py-8">
<Text className="text-sm text-gray-400"></Text>
</View>
) : (
floorList.map((item, index) => (
<Cell
key={item.dictDataId || index}
title={item.dictDataName || item.label || ''}
extra={
selectedFloor?.dictDataId === item.dictDataId ? (
<Text className="text-sm text-blue-500"></Text>
) : null
}
onClick={() => handleSelectFloor(item)}
/>
))
)}
</View>
</View>
</Popup>
{/* 房号选择弹出层 */}
<Popup
visible={showRoomPicker}
position="bottom"
round
onClose={() => setShowRoomPicker(false)}
style={{height: '70%'}}
>
<View className="flex flex-col h-full">
{/* 标题栏 */}
<View className="flex items-center justify-between px-4 py-3 border-b border-gray-100">
<Text className="text-base font-semibold text-gray-800"></Text>
<View onClick={() => setShowRoomPicker(false)}>
<Text className="text-sm text-blue-500"></Text>
</View>
</View>
{/* 搜索框 */}
<View className="px-3 py-2">
<SearchBar
value={roomSearch}
placeholder="搜索房号"
onChange={handleRoomSearch}
/>
</View>
{/* 列表 */}
<View className="flex-1 overflow-y-auto">
{roomLoading ? (
<View className="flex justify-center items-center py-8">
<Loading></Loading>
</View>
) : roomList.length === 0 ? (
<View className="flex justify-center items-center py-8">
<Text className="text-sm text-gray-400"></Text>
</View>
) : (
roomList.map((item, index) => (
<Cell
key={item.dictDataId || index}
title={item.dictDataName || item.label || ''}
extra={
selectedRoom?.dictDataId === item.dictDataId ? (
<Text className="text-sm text-blue-500"></Text>
) : null
}
onClick={() => handleSelectRoom(item)}
/>
))
)}
</View>
</View>
</Popup>
{/* 审核状态显示(仅在编辑模式下显示) */}
{isEditMode && (
<CellGroup>
{/*<Cell*/}
{/* title={'审核状态'}*/}
{/* extra={*/}
{/* <span style={{*/}
{/* color: FormData?.applyStatus === 20 ? '#52c41a' :*/}
{/* FormData?.applyStatus === 30 ? '#ff4d4f' : '#faad14'*/}
{/* }}>*/}
{/* {getApplyStatusText(FormData?.applyStatus)}*/}
{/* </span>*/}
{/* }*/}
{/*/>*/}
{FormData?.applyStatus === 20 && (
<Cell title={'签约时间'} extra={FormData?.auditTime || '无'}/>
)}
{FormData?.applyStatus === 30 && (
<Cell title={'驳回原因'} extra={FormData?.rejectReason || '无'}/>
)}
</CellGroup>
)}
{/* 底部浮动按钮 */}
{(!isEditMode || FormData?.applyStatus === 10) && (
<FixedButton
icon={<Edit/>}
text={'立即提交'}
onClick={handleFixedButtonClick}
/>
)}
</>
);
};
export default AddShopDealerApply;