diff --git a/.workbuddy/expert-history.json b/.workbuddy/expert-history.json index c0ada6c..2f8c83e 100644 --- a/.workbuddy/expert-history.json +++ b/.workbuddy/expert-history.json @@ -22,7 +22,18 @@ "usedAt": 1776102350082, "industryId": "all" } + ], + "06a306d869f24d2eb36c381b2a67c63e": [ + { + "expertId": "SeniorDeveloper", + "name": "吴八哥", + "profession": "高级开发工程师", + "avatarUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/avatars/02-Engineering/SeniorDeveloper/SeniorDeveloper.png", + "promptUrl": "https://acc-1258344699.cos.accelerate.myqcloud.com/workbuddy/experts/experts/02-Engineering/SeniorDeveloper/SeniorDeveloper_zh.md", + "usedAt": 1776328275045, + "industryId": "all" + } ] }, - "lastUpdated": 1776132867828 + "lastUpdated": 1776330360601 } \ No newline at end of file diff --git a/.workbuddy/memory/2026-04-14.md b/.workbuddy/memory/2026-04-14.md index 81ba2eb..d03e066 100644 --- a/.workbuddy/memory/2026-04-14.md +++ b/.workbuddy/memory/2026-04-14.md @@ -54,3 +54,31 @@ - 返回值用 `ApiResult` + `success()`,Controller继承 `BaseController` - Service层获取登录用户:`SecurityContextHolder.getContext().getAuthentication().getPrincipal()` 强转 `User` - 按ID查询用户:`userService.getByIdRel(userId)`(非 `getUserById`) + +### 推荐客户模块重构(2026-04-16) +- **包名变更**:`cms.LeadReferralController` → `app.recommendation.LeadReferralController` +- **API路径**:`/lead/referral` → `/app/lead/referral` +- **数据库表前缀**:`cms_contact_lead` → `app_lead_referral` + +#### 新建文件清单 +| 项目 | 文件 | +|------|------| +| Java后端 | `app/recommendation/entity/LeadReferral.java` | +| | `app/recommendation/entity/ReferrerInfo.java` | +| | `app/recommendation/entity/ReferralSettlement.java` | +| | `app/recommendation/mapper/LeadReferralMapper.java` | +| | `app/recommendation/mapper/ReferrerInfoMapper.java` | +| | `app/recommendation/mapper/ReferralSettlementMapper.java` | +| | `app/recommendation/service/LeadReferralService.java` | +| | `app/recommendation/service/impl/LeadReferralServiceImpl.java` | +| | `app/recommendation/controller/LeadReferralController.java`(小程序端) | +| | `app/recommendation/controller/LeadReferralAdminController.java`(后台管理) | +| | `app/recommendation/param/LeadReferralParam.java` | +| | `resources/db/sql/app_lead_referral.sql` | +| 小程序 | `src/api/app/referral.ts`(新API) | +| | `src/recommendation/index.tsx`(独立推荐页面) | +| | `src/recommendation/index.scss` | +| | `src/dealer/referral/index.tsx`(改用新API) | +| | `src/app.config.ts`(新增recommendation子包路由) | +| Vue后台 | `src/api/app/referral.ts` | +| | `src/views/cms/recommendation/index.vue` | diff --git a/src/api/app/referral.ts b/src/api/app/referral.ts new file mode 100644 index 0000000..190e3c8 --- /dev/null +++ b/src/api/app/referral.ts @@ -0,0 +1,112 @@ +/** + * 推荐客户 API(app 模块) + * 小程序端调用 + */ +import request from '@/utils/request' + +/** + * 报备参数 + */ +export interface ReferralParam { + customerName: string + customerPhone: string + customerCompany?: string + requirement?: string + appointmentTime?: string + remarks?: string +} + +/** + * 推荐记录 + */ +export interface ReferralRecord { + id: number + referralCode: string + referrerId?: number + referrerName?: string + referrerPhone?: string + customerName: string + customerPhone: string + customerCompany?: string + requirement?: string + appointmentTime?: string + remarks?: string + referralFee: string + referralStatus: number + referralStatusText?: string + invalidReason?: string + invalidTime?: string + confirmedTime?: string + settledTime?: string + createTime: string +} + +/** + * 我的推荐统计 + */ +export interface ReferralStats { + totalCount: number + pendingCount: number + validCount: number + settledCount: number + pendingAmount: string + referralCode?: string +} + +/** + * 分页结果 + */ +export interface PageResult { + list: T[] + total: number + pageNum: number + pageSize: number + pages: number +} + +/** + * 报备客户 + */ +export function addReferral(data: ReferralParam) { + return request.post<{ code: number; message: string; data: ReferralRecord }>( + '/app/lead/referral/add', + data + ) +} + +/** + * 获取我的推荐码 + */ +export function getMyReferralCode() { + return request.get<{ code: number; message: string; data: string }>( + '/app/lead/referral/my/code' + ) +} + +/** + * 获取我的推荐记录(分页) + */ +export function getMyReferrals(params: { pageNum?: number; pageSize?: number }) { + return request.get<{ code: number; message: string; data: PageResult }>( + '/app/lead/referral/my', + { params } + ) +} + +/** + * 获取我的推荐统计 + */ +export function getMyStats() { + return request.get<{ code: number; message: string; data: ReferralStats }>( + '/app/lead/referral/my/stats' + ) +} + +/** + * 根据推荐码获取推荐人信息 + */ +export function getReferrerByCode(code: string) { + return request.get<{ code: number; message: string; data: { referrerId: number; referrerName: string; referralCode: string } }>( + `/app/lead/referral/referrer/${code}` + ) +} diff --git a/src/api/cms/cmsNavigation/index.ts b/src/api/cms/cmsNavigation/index.ts index c293d18..299e3b9 100644 --- a/src/api/cms/cmsNavigation/index.ts +++ b/src/api/cms/cmsNavigation/index.ts @@ -105,8 +105,8 @@ export async function getCmsNavigation(id: number) { const res = await request.get>( '/cms/cms-navigation/' + id ); - if (res.code === 0 && res.data) { - return res.data; + if (res.code === 0) { + return res.data ?? null; } return Promise.reject(new Error(res.message)); } diff --git a/src/api/shop/shopDealerReferee/index.ts b/src/api/shop/shopDealerReferee/index.ts index 1c49451..7c5b360 100644 --- a/src/api/shop/shopDealerReferee/index.ts +++ b/src/api/shop/shopDealerReferee/index.ts @@ -94,8 +94,8 @@ export async function getShopDealerReferee(id: number) { const res = await request.get>( '/shop/shop-dealer-referee/' + id ); - if (res.code === 0 && res.data) { - return res.data; + if (res.code === 0) { + return res.data ?? null; } return Promise.reject(new Error(res.message)); } @@ -107,8 +107,8 @@ export async function getShopDealerRefereeByUserId(userId: number) { const res = await request.get>( '/shop/shop-dealer-referee/getByUserId/' + userId ); - if (res.code === 0 && res.data) { - return res.data; + if (res.code === 0) { + return res.data ?? null; } return Promise.reject(new Error(res.message)); } diff --git a/src/app.config.ts b/src/app.config.ts index 0c40490..e41d37d 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -72,7 +72,14 @@ export default defineAppConfig({ "customer/trading", "wechat/index", "bank/index", - "bank/add" + "bank/add", + "referral/index" + ] + }, + { + "root": "recommendation", + "pages": [ + "index" ] }, // { diff --git a/src/dealer/customer/add.tsx b/src/dealer/customer/add.tsx index 9e557e6..e0d5209 100644 --- a/src/dealer/customer/add.tsx +++ b/src/dealer/customer/add.tsx @@ -32,20 +32,22 @@ const AddShopDealerApply = () => { const DUP_CHECK_MAX_PAGES = 50; // 房号信息:用 dealerCode 存储唯一键,dealerName 存储展示文案 - const buildHouseKey = (community: string, buildingNo: string, unitNo: string | undefined, roomNo: string) => { + 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, r].join('|'); + return [c, b, u, f, r].join('|'); }; - const buildHouseDisplay = (community: string, buildingNo: string, unitNo: string | undefined, roomNo: string) => { + 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}单元` : ''}${r ? `${r}号` : ''}`; + return `${c}${b ? `${b}栋` : ''}${u ? `${u}单元` : ''}${f ? `${f}楼` : ''}${r ? `${r}号` : ''}`; }; const parseHouseKey = (key?: string) => { @@ -54,7 +56,8 @@ const AddShopDealerApply = () => { community: parts[0] || '', buildingNo: parts[1] || '', unitNo: parts[2] || '', - roomNo: parts[3] || '', + floorNo: parts[3] || '', + roomNo: parts[4] || '', }; }; @@ -78,6 +81,34 @@ const AddShopDealerApply = () => { const [communityLoading, setCommunityLoading] = useState(false) const [selectedCommunity, setSelectedCommunity] = useState(null) + // 楼栋选择状态 + const [showBuildingPicker, setShowBuildingPicker] = useState(false) + const [buildingSearch, setBuildingSearch] = useState('') + const [buildingList, setBuildingList] = useState([]) + const [buildingLoading, setBuildingLoading] = useState(false) + const [selectedBuilding, setSelectedBuilding] = useState(null) + + // 单元选择状态 + const [showUnitPicker, setShowUnitPicker] = useState(false) + const [unitSearch, setUnitSearch] = useState('') + const [unitList, setUnitList] = useState([]) + const [unitLoading, setUnitLoading] = useState(false) + const [selectedUnit, setSelectedUnit] = useState(null) + + // 楼层选择状态 + const [showFloorPicker, setShowFloorPicker] = useState(false) + const [floorSearch, setFloorSearch] = useState('') + const [floorList, setFloorList] = useState([]) + const [floorLoading, setFloorLoading] = useState(false) + const [selectedFloor, setSelectedFloor] = useState(null) + + // 房号选择状态 + const [showRoomPicker, setShowRoomPicker] = useState(false) + const [roomSearch, setRoomSearch] = useState('') + const [roomList, setRoomList] = useState([]) + const [roomLoading, setRoomLoading] = useState(false) + const [selectedRoom, setSelectedRoom] = useState(null) + // 获取审核状态文字 const getApplyStatusText = (status?: number) => { switch (status) { @@ -92,8 +123,6 @@ const AddShopDealerApply = () => { } } - console.log(getApplyStatusText) - // 处理签约时间选择 const handleApplyTimeConfirm = (param: string) => { const selectedDate = param[3] // 选中的日期字符串 (YYYY-M-D) @@ -212,7 +241,7 @@ const AddShopDealerApply = () => { const list = await listDictData({ dictCode: 'xiaoqu' }) // 过滤搜索关键词 if (keyword) { - setCommunityList(list.filter((item: DictData) => + setCommunityList(list.filter((item: DictData) => (item.dictDataName || '').includes(keyword) || (item.label || '').includes(keyword) )) @@ -261,6 +290,222 @@ const AddShopDealerApply = () => { } } + // 加载楼栋列表 + 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 => { @@ -334,13 +579,14 @@ const AddShopDealerApply = () => { return s.replace(/\s+/g, '').toUpperCase(); }; - const normalizeHouseNoPart = (raw: string, kind: 'building' | 'unit' | 'room') => { + 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) @@ -355,12 +601,13 @@ const AddShopDealerApply = () => { return s; }; - const buildHouseKeyNormalized = (community: string, buildingNo: string, unitNo: string | undefined, roomNo: string) => { + 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, r].join('|'); + return [c, b, u, f, r].join('|'); }; const getNormalizedHouseKeyFromApply = (apply: ShopDealerApply) => { @@ -369,6 +616,7 @@ const AddShopDealerApply = () => { parsed.community || apply.address || '', parsed.buildingNo || '', parsed.unitNo || '', + parsed.floorNo || '', parsed.roomNo || '' ); }; @@ -412,11 +660,15 @@ const AddShopDealerApply = () => { return; } if (!values.buildingNo || values.buildingNo.trim() === '') { - Taro.showToast({title: '请填写楼栋号', icon: 'error'}); + 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'}); + Taro.showToast({title: '请选择房号', icon: 'error'}); return; } if (!values.realName || values.realName.trim() === '') { @@ -471,10 +723,10 @@ const AddShopDealerApply = () => { ? reporterDealerUser.refereeId : undefined; - const houseKeyRaw = buildHouseKey(values.address, values.buildingNo, values.unitNo, values.roomNo); - const houseKeyNormalized = buildHouseKeyNormalized(values.address, values.buildingNo, values.unitNo, values.roomNo); + 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.roomNo); + const houseDisplay = buildHouseDisplay(values.address, values.buildingNo, values.unitNo, values.floorNo, values.roomNo); // 新增报备:提交前检查房号是否已报备(按 小区+楼栋+单元+房号 判断,且做规范化) if (!isEditMode) { @@ -551,8 +803,8 @@ const AddShopDealerApply = () => { const expirationTime = isEditMode ? existingApply?.expirationTime : calculateExpirationTime(); // 准备提交的数据 - // 避免把表单里的楼栋/单元/房号等临时字段原样提交给后端 - const {buildingNo, unitNo, roomNo, ...restValues} = values; + // 避免把表单里的楼栋/单元/楼层/房号等临时字段原样提交给后端 + const {buildingNo, unitNo, floorNo, roomNo, ...restValues} = values; const submitData = { ...restValues, type: 4, @@ -639,7 +891,7 @@ const AddShopDealerApply = () => { }) }, []); // 依赖用户ID,当用户变化时重新加载 - // 编辑模式下,从 dealerCode 反解出楼栋/单元/房号,回填表单(只读展示) + // 编辑模式下,从 dealerCode 反解出楼栋/单元/楼层/房号,回填表单(只读展示) useEffect(() => { if (!formRef.current || !FormData) return; const parsed = parseHouseKey(FormData.dealerCode); @@ -648,6 +900,7 @@ const AddShopDealerApply = () => { address: communityValue, buildingNo: parsed.buildingNo, unitNo: parsed.unitNo, + floorNo: parsed.floorNo, roomNo: parsed.roomNo, realName: FormData.realName, mobile: FormData.mobile @@ -659,6 +912,34 @@ const AddShopDealerApply = () => { 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) { @@ -704,15 +985,126 @@ const AddShopDealerApply = () => { } onClick={isEditMode ? undefined : openCommunityPicker} /> - {/* 隐藏字段,用于表单提交 */} - - + {/* 隐藏字段,通过 ref.setFieldsValue 设置 */} + + - - + {/* 楼栋选择 */} + + {selectedBuilding ? ( + + {selectedBuilding.dictDataName || selectedBuilding.label} + {!isEditMode && ( + { e.stopPropagation(); handleClearBuilding(); }} + className="flex items-center px-1" + > + + + )} + + ) : ( + 请选择 + )} + {!isEditMode && } + + } + onClick={isEditMode ? undefined : openBuildingPicker} + /> + {/* 隐藏字段,通过 ref.setFieldsValue 设置 */} + + - - + {/* 单元选择 */} + + {selectedUnit ? ( + + {selectedUnit.dictDataName || selectedUnit.label} + {!isEditMode && ( + { e.stopPropagation(); handleClearUnit(); }} + className="flex items-center px-1" + > + + + )} + + ) : ( + 请选择 + )} + {!isEditMode && } + + } + onClick={isEditMode ? undefined : openUnitPicker} + /> + {/* 隐藏字段,通过 ref.setFieldsValue 设置 */} + + + + {/* 楼层选择 */} + + {selectedFloor ? ( + + {selectedFloor.dictDataName || selectedFloor.label} + {!isEditMode && ( + { e.stopPropagation(); handleClearFloor(); }} + className="flex items-center px-1" + > + + + )} + + ) : ( + 请选择 + )} + {!isEditMode && } + + } + onClick={isEditMode ? undefined : openFloorPicker} + /> + {/* 隐藏字段,通过 ref.setFieldsValue 设置 */} + + + + {/* 房号选择 */} + + {selectedRoom ? ( + + {selectedRoom.dictDataName || selectedRoom.label} + {!isEditMode && ( + { e.stopPropagation(); handleClearRoom(); }} + className="flex items-center px-1" + > + + + )} + + ) : ( + 请选择 + )} + {!isEditMode && } + + } + onClick={isEditMode ? undefined : openRoomPicker} + /> + {/* 隐藏字段,通过 ref.setFieldsValue 设置 */} + + @@ -748,32 +1140,24 @@ const AddShopDealerApply = () => { {isEditMode && ( <> - + - setShowApplyTimePicker(true)} - > - - - - {applyTime ? formatDateForDisplay(applyTime) : '请选择签约时间'} - - + setShowApplyTimePicker(true)}> + - setShowContractTimePicker(true)} - > - - - - {contractTime ? formatDateForDisplay(contractTime) : '请选择合同生效起止时间'} - - + setShowContractTimePicker(true)}> + {/**/} @@ -913,6 +1297,214 @@ const AddShopDealerApply = () => { + {/* 楼栋选择弹出层 */} + setShowBuildingPicker(false)} + style={{height: '70%'}} + > + + {/* 标题栏 */} + + 选择楼栋 + setShowBuildingPicker(false)}> + 取消 + + + {/* 搜索框 */} + + + + {/* 列表 */} + + {buildingLoading ? ( + + 加载中 + + ) : buildingList.length === 0 ? ( + + 暂无楼栋数据 + + ) : ( + buildingList.map((item, index) => ( + 已选 + ) : null + } + onClick={() => handleSelectBuilding(item)} + /> + )) + )} + + + + + {/* 单元选择弹出层 */} + setShowUnitPicker(false)} + style={{height: '70%'}} + > + + {/* 标题栏 */} + + 选择单元 + setShowUnitPicker(false)}> + 取消 + + + {/* 搜索框 */} + + + + {/* 列表 */} + + {unitLoading ? ( + + 加载中 + + ) : unitList.length === 0 ? ( + + 暂无单元数据 + + ) : ( + unitList.map((item, index) => ( + 已选 + ) : null + } + onClick={() => handleSelectUnit(item)} + /> + )) + )} + + + + + {/* 楼层选择弹出层 */} + setShowFloorPicker(false)} + style={{height: '70%'}} + > + + {/* 标题栏 */} + + 选择楼层 + setShowFloorPicker(false)}> + 取消 + + + {/* 搜索框 */} + + + + {/* 列表 */} + + {floorLoading ? ( + + 加载中 + + ) : floorList.length === 0 ? ( + + 暂无楼层数据 + + ) : ( + floorList.map((item, index) => ( + 已选 + ) : null + } + onClick={() => handleSelectFloor(item)} + /> + )) + )} + + + + + {/* 房号选择弹出层 */} + setShowRoomPicker(false)} + style={{height: '70%'}} + > + + {/* 标题栏 */} + + 选择房号 + setShowRoomPicker(false)}> + 取消 + + + {/* 搜索框 */} + + + + {/* 列表 */} + + {roomLoading ? ( + + 加载中 + + ) : roomList.length === 0 ? ( + + 暂无房号数据 + + ) : ( + roomList.map((item, index) => ( + 已选 + ) : null + } + onClick={() => handleSelectRoom(item)} + /> + )) + )} + + + + {/* 审核状态显示(仅在编辑模式下显示) */} {isEditMode && ( diff --git a/src/dealer/referral/index.tsx b/src/dealer/referral/index.tsx index bb8c57e..6c132da 100644 --- a/src/dealer/referral/index.tsx +++ b/src/dealer/referral/index.tsx @@ -3,7 +3,7 @@ import {View, Text, ScrollView, Input, Button} from '@tarojs/components' import {ConfigProvider, Field, Cell, CellGroup, Toast} from '@nutui/nutui-react-taro' import {useDealerUser} from '@/hooks/useDealerUser' import Taro from '@tarojs/taro' -import {addReferral, getReferralList, getReferralStats} from '@/api/shop/referral' +import {addReferral, getMyReferrals, getMyStats} from '@/api/app/referral' import './index.scss' // 状态映射 @@ -50,13 +50,13 @@ const ReferralPage: React.FC = () => { setLoading(true) // 获取统计 - const statsRes = await getReferralStats(dealerUser.userId) + const statsRes = await getMyStats() if (statsRes.data.code === 0) { setStats(statsRes.data.data) } // 获取列表 - const listRes = await getReferralList({pageNum: 1, pageSize: 10}) + const listRes = await getMyReferrals({pageNum: 1, pageSize: 10}) if (listRes.data.code === 0) { setRecords(listRes.data.data.list || []) setHasMore(listRes.data.data.list?.length === 10) @@ -137,7 +137,7 @@ const ReferralPage: React.FC = () => { try { const nextPage = page + 1 - const res = await getReferralList({pageNum: nextPage, pageSize: 10}) + const res = await getMyReferrals({pageNum: nextPage, pageSize: 10}) if (res.data.code === 0 && res.data.data.list) { setRecords(prev => [...prev, ...res.data.data.list]) setPage(nextPage) diff --git a/src/recommendation/index.scss b/src/recommendation/index.scss new file mode 100644 index 0000000..6861dc9 --- /dev/null +++ b/src/recommendation/index.scss @@ -0,0 +1,156 @@ +.referral-page { + min-height: 100vh; + background-color: #f5f5f7; +} + +.stats-section { + padding: 24px 16px; + color: #fff; + + .stats-title { + margin-bottom: 16px; + text-align: center; + } + + .stats-grid { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 8px; + + .stat-item { + text-align: center; + + .stat-value { + font-size: 20px; + font-weight: bold; + margin-bottom: 4px; + } + + .stat-label { + font-size: 12px; + } + } + } +} + +.form-section { + padding: 0 16px 16px; + + .section-title { + margin-bottom: 12px; + } + + .form-card { + background: #fff; + border-radius: 12px; + padding: 16px 0; + + .form-field { + display: flex; + align-items: center; + padding: 12px 16px; + border-bottom: 1px solid #f0f0f0; + + &:last-of-type { + border-bottom: none; + } + + &.form-field-area { + flex-direction: column; + align-items: flex-start; + } + } + + .form-label { + width: 70px; + font-size: 15px; + color: #333; + flex-shrink: 0; + } + + .form-input { + flex: 1; + font-size: 15px; + text-align: right; + } + + .submit-btn { + padding: 16px; + + button { + width: 100%; + border-radius: 8px; + height: 44px; + line-height: 44px; + } + } + + .tips { + padding: 0 16px 8px; + text-align: center; + } + } +} + +.records-section { + padding: 0 16px 16px; + + .section-title { + margin-bottom: 12px; + } + + .empty-state { + background: #fff; + border-radius: 12px; + padding: 40px; + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + } + + .record-card { + background: #fff; + border-radius: 12px; + padding: 16px; + margin-bottom: 12px; + + .record-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; + + .customer-info { + display: flex; + align-items: center; + } + + .status-tag { + padding: 4px 10px; + border-radius: 12px; + font-size: 12px; + font-weight: 500; + } + } + + .record-body { + .record-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 0; + + &:not(:last-child) { + border-bottom: 1px solid #f0f0f0; + } + } + } + } + + .no-more { + text-align: center; + padding: 16px; + } +} diff --git a/src/recommendation/index.tsx b/src/recommendation/index.tsx new file mode 100644 index 0000000..2c8ed5c --- /dev/null +++ b/src/recommendation/index.tsx @@ -0,0 +1,319 @@ +import React, {useState, useEffect} from 'react' +import {View, Text, ScrollView, Input, Button} from '@tarojs/components' +import {Toast} from '@nutui/nutui-react-taro' +import {useDealerUser} from '@/hooks/useDealerUser' +import Taro from '@tarojs/taro' +import {addReferral, getMyReferrals, getMyStats} from '@/api/app/referral' +import './index.scss' + +// 状态映射 +const STATUS_MAP: Record = { + 0: {text: '待确认', color: '#ff9800'}, + 1: {text: '有效', color: '#4caf50'}, + 2: {text: '无效', color: '#9e9e9e'}, + 3: {text: '已结算', color: '#2196f3'} +} + +const ReferralPage: React.FC = () => { + const {dealerUser} = useDealerUser() + const [loading, setLoading] = useState(false) + const [submitting, setSubmitting] = useState(false) + + // 表单数据 + const [formData, setFormData] = useState({ + customerName: '', + customerPhone: '', + customerCompany: '', + requirement: '', + remarks: '' + }) + + // 统计 + const [stats, setStats] = useState({ + totalCount: 0, + pendingCount: 0, + validCount: 0, + settledCount: 0, + pendingAmount: '0.00', + referralCode: '' + }) + + // 记录列表 + const [records, setRecords] = useState([]) + const [page, setPage] = useState(1) + const [hasMore, setHasMore] = useState(true) + + // 加载数据 + const loadData = async () => { + if (!dealerUser?.userId) return + + try { + setLoading(true) + + // 获取统计 + const statsRes = await getMyStats() + if (statsRes.data.code === 0) { + setStats(statsRes.data.data) + } + + // 获取列表 + const listRes = await getMyReferrals({pageNum: 1, pageSize: 10}) + if (listRes.data.code === 0) { + setRecords(listRes.data.data.list || []) + setPage(1) + setHasMore(listRes.data.data.list?.length === 10) + } + } catch (error) { + console.error('加载失败', error) + } finally { + setLoading(false) + } + } + + useEffect(() => { + loadData() + }, [dealerUser]) + + // 输入处理 + const handleInput = (field: string, value: string) => { + setFormData(prev => ({...prev, [field]: value})) + } + + // 表单验证 + const validateForm = () => { + if (!formData.customerName.trim()) { + Toast.text('请输入客户姓名') + return false + } + if (!formData.customerPhone.trim()) { + Toast.text('请输入客户电话') + return false + } + if (!/^1[3-9]\d{9}$/.test(formData.customerPhone)) { + Toast.text('请输入正确的手机号') + return false + } + return true + } + + // 提交报备 + const handleSubmit = async () => { + if (!validateForm()) return + + try { + setSubmitting(true) + const res = await addReferral(formData) + + if (res.data.code === 0) { + Toast.text('报备成功!') + setFormData({ + customerName: '', + customerPhone: '', + customerCompany: '', + requirement: '', + remarks: '' + }) + loadData() + } else { + Toast.text(res.data.message || '报备失败') + } + } catch (error: any) { + Toast.text(error.message || '报备失败') + } finally { + setSubmitting(false) + } + } + + // 拨打电话 + const handleCall = (phone: string) => { + if (phone) { + Taro.makePhoneCall({phoneNumber: phone}) + } + } + + // 加载更多 + const loadMore = async () => { + if (!hasMore || loading) return + + try { + const nextPage = page + 1 + const res = await getMyReferrals({pageNum: nextPage, pageSize: 10}) + if (res.data.code === 0 && res.data.data.list) { + setRecords(prev => [...prev, ...res.data.data.list]) + setPage(nextPage) + setHasMore(res.data.data.list.length === 10) + } + } catch (error) { + console.error('加载更多失败', error) + } + } + + return ( + + {/* 头部统计 */} + + + 我的推荐奖励 + + + + {stats.totalCount} + 总推荐 + + + {stats.pendingCount} + 待确认 + + + {stats.validCount} + 有效 + + + ¥{stats.pendingAmount} + 待结算 + + + + + {/* 报备表单 */} + + + 推荐新客户 + + + + 客户姓名 + handleInput('customerName', e.detail.value)} + /> + + + 联系电话 + handleInput('customerPhone', e.detail.value)} + /> + + + 公司名称 + handleInput('customerCompany', e.detail.value)} + /> + + + 需求描述 + handleInput('requirement', e.detail.value)} + style={{height: '80px', textAlign: 'left'}} + /> + + + + + + + + + 报备成功后,业务员会尽快联系您的客户。成交后您将获得相应佣金奖励。 + + + + + + {/* 推荐记录 */} + + + 我的推荐记录 + + + {records.length === 0 ? ( + + 暂无推荐记录 + 快去推荐客户吧 + + ) : ( + + {records.map((item) => { + const statusInfo = STATUS_MAP[item.referralStatus] || STATUS_MAP[0] + return ( + + + + {item.customerName} + handleCall(item.customerPhone)} + > + {item.customerPhone} + + + + {statusInfo.text} + + + + + + 推荐时间 + {item.createTime} + + {parseFloat(item.referralFee) > 0 && ( + + 奖励金额 + + ¥{item.referralFee} + + + )} + + + ) + })} + + {!hasMore && records.length > 0 && ( + + 没有更多了 + + )} + + )} + + + {/* 底部安全区 */} + + + ) +} + +export default ReferralPage