feat(referral): 新增楼栋单元楼层房号精细选择功能

- 将房号唯一键增加楼层字段,修改相关函数支持楼层处理
- 新增楼栋、单元、楼层、房号的选择状态和搜索过滤功能
- 实现楼栋、单元、楼层、房号的选择弹窗和清除按钮
- 表单改用选择控件替代输入框,隐藏字段同步表单数据
- 修改表单校验,验证楼栋、楼层、房号字段必填
- 编辑模式支持从dealerCode解析回填楼栋、单元、楼层、房号
- 优化房号规范化逻辑,去除楼层相关后缀
- 代码中统一使用规范化后的楼栋单元楼层房号构造唯一
This commit is contained in:
2026-04-16 17:09:58 +08:00
parent 8128e2ffb2
commit 099855e121
10 changed files with 1284 additions and 59 deletions

View File

@@ -22,7 +22,18 @@
"usedAt": 1776102350082, "usedAt": 1776102350082,
"industryId": "all" "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
} }

View File

@@ -54,3 +54,31 @@
- 返回值用 `ApiResult<T>` + `success()`Controller继承 `BaseController` - 返回值用 `ApiResult<T>` + `success()`Controller继承 `BaseController`
- Service层获取登录用户`SecurityContextHolder.getContext().getAuthentication().getPrincipal()` 强转 `User` - Service层获取登录用户`SecurityContextHolder.getContext().getAuthentication().getPrincipal()` 强转 `User`
- 按ID查询用户`userService.getByIdRel(userId)`(非 `getUserById` - 按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` |

112
src/api/app/referral.ts Normal file
View File

@@ -0,0 +1,112 @@
/**
* 推荐客户 APIapp 模块)
* 小程序端调用
*/
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<T> {
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<ReferralRecord> }>(
'/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}`
)
}

View File

@@ -105,8 +105,8 @@ export async function getCmsNavigation(id: number) {
const res = await request.get<ApiResult<CmsNavigation>>( const res = await request.get<ApiResult<CmsNavigation>>(
'/cms/cms-navigation/' + id '/cms/cms-navigation/' + id
); );
if (res.code === 0 && res.data) { if (res.code === 0) {
return res.data; return res.data ?? null;
} }
return Promise.reject(new Error(res.message)); return Promise.reject(new Error(res.message));
} }

View File

@@ -94,8 +94,8 @@ export async function getShopDealerReferee(id: number) {
const res = await request.get<ApiResult<ShopDealerReferee>>( const res = await request.get<ApiResult<ShopDealerReferee>>(
'/shop/shop-dealer-referee/' + id '/shop/shop-dealer-referee/' + id
); );
if (res.code === 0 && res.data) { if (res.code === 0) {
return res.data; return res.data ?? null;
} }
return Promise.reject(new Error(res.message)); return Promise.reject(new Error(res.message));
} }
@@ -107,8 +107,8 @@ export async function getShopDealerRefereeByUserId(userId: number) {
const res = await request.get<ApiResult<ShopDealerReferee>>( const res = await request.get<ApiResult<ShopDealerReferee>>(
'/shop/shop-dealer-referee/getByUserId/' + userId '/shop/shop-dealer-referee/getByUserId/' + userId
); );
if (res.code === 0 && res.data) { if (res.code === 0) {
return res.data; return res.data ?? null;
} }
return Promise.reject(new Error(res.message)); return Promise.reject(new Error(res.message));
} }

View File

@@ -72,7 +72,14 @@ export default defineAppConfig({
"customer/trading", "customer/trading",
"wechat/index", "wechat/index",
"bank/index", "bank/index",
"bank/add" "bank/add",
"referral/index"
]
},
{
"root": "recommendation",
"pages": [
"index"
] ]
}, },
// { // {

View File

@@ -32,20 +32,22 @@ const AddShopDealerApply = () => {
const DUP_CHECK_MAX_PAGES = 50; const DUP_CHECK_MAX_PAGES = 50;
// 房号信息:用 dealerCode 存储唯一键dealerName 存储展示文案 // 房号信息:用 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 c = (community || '').trim();
const b = (buildingNo || '').trim(); const b = (buildingNo || '').trim();
const u = (unitNo || '').trim(); const u = (unitNo || '').trim();
const f = (floorNo || '').trim();
const r = (roomNo || '').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 c = (community || '').trim();
const b = (buildingNo || '').trim(); const b = (buildingNo || '').trim();
const u = (unitNo || '').trim(); const u = (unitNo || '').trim();
const f = (floorNo || '').trim();
const r = (roomNo || '').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) => { const parseHouseKey = (key?: string) => {
@@ -54,7 +56,8 @@ const AddShopDealerApply = () => {
community: parts[0] || '', community: parts[0] || '',
buildingNo: parts[1] || '', buildingNo: parts[1] || '',
unitNo: parts[2] || '', unitNo: parts[2] || '',
roomNo: parts[3] || '', floorNo: parts[3] || '',
roomNo: parts[4] || '',
}; };
}; };
@@ -78,6 +81,34 @@ const AddShopDealerApply = () => {
const [communityLoading, setCommunityLoading] = useState<boolean>(false) const [communityLoading, setCommunityLoading] = useState<boolean>(false)
const [selectedCommunity, setSelectedCommunity] = useState<DictData | null>(null) 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) => { const getApplyStatusText = (status?: number) => {
switch (status) { switch (status) {
@@ -92,8 +123,6 @@ const AddShopDealerApply = () => {
} }
} }
console.log(getApplyStatusText)
// 处理签约时间选择 // 处理签约时间选择
const handleApplyTimeConfirm = (param: string) => { const handleApplyTimeConfirm = (param: string) => {
const selectedDate = param[3] // 选中的日期字符串 (YYYY-M-D) const selectedDate = param[3] // 选中的日期字符串 (YYYY-M-D)
@@ -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天后 // 计算保护期过期时间15天后
const calculateExpirationTime = (): string => { const calculateExpirationTime = (): string => {
@@ -334,13 +579,14 @@ const AddShopDealerApply = () => {
return s.replace(/\s+/g, '').toUpperCase(); 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(); let s = toHalfWidth(normalizeText(raw)).toUpperCase();
s = s.replace(/\s+/g, ''); s = s.replace(/\s+/g, '');
// 去掉常见后缀/装饰词 // 去掉常见后缀/装饰词
if (kind === 'building') s = s.replace(/(号楼|栋|幢|楼)$/g, ''); if (kind === 'building') s = s.replace(/(号楼|栋|幢|楼)$/g, '');
if (kind === 'unit') 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, ''); if (kind === 'room') s = s.replace(/(室|房|号)$/g, '');
// 只保留数字与字母,统一分隔符差异(如 12-01 / 12#01 // 只保留数字与字母,统一分隔符差异(如 12-01 / 12#01
@@ -355,12 +601,13 @@ const AddShopDealerApply = () => {
return s; 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 c = normalizeCommunity(community);
const b = normalizeHouseNoPart(buildingNo, 'building'); const b = normalizeHouseNoPart(buildingNo, 'building');
const u = normalizeHouseNoPart(unitNo || '', 'unit'); const u = normalizeHouseNoPart(unitNo || '', 'unit');
const f = normalizeHouseNoPart(floorNo || '', 'floor');
const r = normalizeHouseNoPart(roomNo, 'room'); const r = normalizeHouseNoPart(roomNo, 'room');
return [c, b, u, r].join('|'); return [c, b, u, f, r].join('|');
}; };
const getNormalizedHouseKeyFromApply = (apply: ShopDealerApply) => { const getNormalizedHouseKeyFromApply = (apply: ShopDealerApply) => {
@@ -369,6 +616,7 @@ const AddShopDealerApply = () => {
parsed.community || apply.address || '', parsed.community || apply.address || '',
parsed.buildingNo || '', parsed.buildingNo || '',
parsed.unitNo || '', parsed.unitNo || '',
parsed.floorNo || '',
parsed.roomNo || '' parsed.roomNo || ''
); );
}; };
@@ -412,11 +660,15 @@ const AddShopDealerApply = () => {
return; return;
} }
if (!values.buildingNo || values.buildingNo.trim() === '') { 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; return;
} }
if (!values.roomNo || values.roomNo.trim() === '') { if (!values.roomNo || values.roomNo.trim() === '') {
Taro.showToast({title: '请填写房号', icon: 'error'}); Taro.showToast({title: '请选择房号', icon: 'error'});
return; return;
} }
if (!values.realName || values.realName.trim() === '') { if (!values.realName || values.realName.trim() === '') {
@@ -471,10 +723,10 @@ const AddShopDealerApply = () => {
? reporterDealerUser.refereeId ? reporterDealerUser.refereeId
: undefined; : undefined;
const houseKeyRaw = buildHouseKey(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.roomNo); const houseKeyNormalized = buildHouseKeyNormalized(values.address, values.buildingNo, values.unitNo, values.floorNo, values.roomNo);
const houseKey = houseKeyNormalized || houseKeyRaw; 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) { if (!isEditMode) {
@@ -551,8 +803,8 @@ const AddShopDealerApply = () => {
const expirationTime = isEditMode ? existingApply?.expirationTime : calculateExpirationTime(); const expirationTime = isEditMode ? existingApply?.expirationTime : calculateExpirationTime();
// 准备提交的数据 // 准备提交的数据
// 避免把表单里的楼栋/单元/房号等临时字段原样提交给后端 // 避免把表单里的楼栋/单元/楼层/房号等临时字段原样提交给后端
const {buildingNo, unitNo, roomNo, ...restValues} = values; const {buildingNo, unitNo, floorNo, roomNo, ...restValues} = values;
const submitData = { const submitData = {
...restValues, ...restValues,
type: 4, type: 4,
@@ -639,7 +891,7 @@ const AddShopDealerApply = () => {
}) })
}, []); // 依赖用户ID当用户变化时重新加载 }, []); // 依赖用户ID当用户变化时重新加载
// 编辑模式下,从 dealerCode 反解出楼栋/单元/房号,回填表单(只读展示) // 编辑模式下,从 dealerCode 反解出楼栋/单元/楼层/房号,回填表单(只读展示)
useEffect(() => { useEffect(() => {
if (!formRef.current || !FormData) return; if (!formRef.current || !FormData) return;
const parsed = parseHouseKey(FormData.dealerCode); const parsed = parseHouseKey(FormData.dealerCode);
@@ -648,6 +900,7 @@ const AddShopDealerApply = () => {
address: communityValue, address: communityValue,
buildingNo: parsed.buildingNo, buildingNo: parsed.buildingNo,
unitNo: parsed.unitNo, unitNo: parsed.unitNo,
floorNo: parsed.floorNo,
roomNo: parsed.roomNo, roomNo: parsed.roomNo,
realName: FormData.realName, realName: FormData.realName,
mobile: FormData.mobile mobile: FormData.mobile
@@ -659,6 +912,34 @@ const AddShopDealerApply = () => {
label: communityValue label: communityValue
} as DictData) } 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]); }, [FormData]);
if (loading) { if (loading) {
@@ -704,15 +985,126 @@ const AddShopDealerApply = () => {
} }
onClick={isEditMode ? undefined : openCommunityPicker} onClick={isEditMode ? undefined : openCommunityPicker}
/> />
<Form.Item name="address" initialValue={FormData?.address} style={{display: 'none'}}/> {/* 隐藏字段,用于表单提交 */} {/* 隐藏字段,通过 ref.setFieldsValue 设置 */}
<Form.Item name="buildingNo" label="楼栋号" required> <Form.Item name="address" style={{display: 'none'}}>
<Input placeholder="3" disabled={isEditMode}/> <View />
</Form.Item> </Form.Item>
<Form.Item name="unitNo" label="单元号"> {/* 楼栋选择 */}
<Input placeholder="1" disabled={isEditMode}/> <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> </Form.Item>
<Form.Item name="roomNo" label="房号" required> {/* 单元选择 */}
<Input placeholder="1201" disabled={isEditMode}/> <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
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> </Form.Item>
<Form.Item name="realName" label="姓名" initialValue={FormData?.realName} required> <Form.Item name="realName" label="姓名" initialValue={FormData?.realName} required>
<Input placeholder="张三" disabled={isEditMode}/> <Input placeholder="张三" disabled={isEditMode}/>
@@ -748,32 +1140,24 @@ const AddShopDealerApply = () => {
{isEditMode && ( {isEditMode && (
<> <>
<Form.Item name="money" label="签约价格" initialValue={FormData?.money} required> <Form.Item name="money" label="签约价格" initialValue={FormData?.money} required>
<Input placeholder="(元/兆瓦时)" disabled={false}/> <Input placeholder="(元/兆瓦时)" />
</Form.Item> </Form.Item>
<Form.Item name="applyTime" label="签约时间" initialValue={FormData?.applyTime}> <Form.Item name="applyTime" label="签约时间" initialValue={FormData?.applyTime}>
<View <View onClick={() => setShowApplyTimePicker(true)}>
className="flex items-center justify-between py-2" <Input
onClick={() => setShowApplyTimePicker(true)} placeholder="点击选择签约时间"
> readonly
<View className="flex items-center"> value={applyTime ? formatDateForDisplay(applyTime) : ''}
<CalendarIcon size={16} color="#999" className="mr-2"/> />
<Text style={{color: applyTime ? '#333' : '#999'}}>
{applyTime ? formatDateForDisplay(applyTime) : '请选择签约时间'}
</Text>
</View>
</View> </View>
</Form.Item> </Form.Item>
<Form.Item name="contractTime" label="合同日期" initialValue={FormData?.contractTime}> <Form.Item name="contractTime" label="合同日期" initialValue={FormData?.contractTime}>
<View <View onClick={() => setShowContractTimePicker(true)}>
className="flex items-center justify-between py-2" <Input
onClick={() => setShowContractTimePicker(true)} placeholder="点击选择合同生效起止时间"
> readonly
<View className="flex items-center"> value={contractTime ? formatDateForDisplay(contractTime) : ''}
<CalendarIcon size={16} color="#999" className="mr-2"/> />
<Text style={{color: contractTime ? '#333' : '#999'}}>
{contractTime ? formatDateForDisplay(contractTime) : '请选择合同生效起止时间'}
</Text>
</View>
</View> </View>
</Form.Item> </Form.Item>
{/*<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>*/} {/*<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>*/}
@@ -913,6 +1297,214 @@ const AddShopDealerApply = () => {
</View> </View>
</Popup> </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 && ( {isEditMode && (
<CellGroup> <CellGroup>

View File

@@ -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 {ConfigProvider, Field, Cell, CellGroup, Toast} from '@nutui/nutui-react-taro'
import {useDealerUser} from '@/hooks/useDealerUser' import {useDealerUser} from '@/hooks/useDealerUser'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import {addReferral, getReferralList, getReferralStats} from '@/api/shop/referral' import {addReferral, getMyReferrals, getMyStats} from '@/api/app/referral'
import './index.scss' import './index.scss'
// 状态映射 // 状态映射
@@ -50,13 +50,13 @@ const ReferralPage: React.FC = () => {
setLoading(true) setLoading(true)
// 获取统计 // 获取统计
const statsRes = await getReferralStats(dealerUser.userId) const statsRes = await getMyStats()
if (statsRes.data.code === 0) { if (statsRes.data.code === 0) {
setStats(statsRes.data.data) 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) { if (listRes.data.code === 0) {
setRecords(listRes.data.data.list || []) setRecords(listRes.data.data.list || [])
setHasMore(listRes.data.data.list?.length === 10) setHasMore(listRes.data.data.list?.length === 10)
@@ -137,7 +137,7 @@ const ReferralPage: React.FC = () => {
try { try {
const nextPage = page + 1 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) { if (res.data.code === 0 && res.data.data.list) {
setRecords(prev => [...prev, ...res.data.data.list]) setRecords(prev => [...prev, ...res.data.data.list])
setPage(nextPage) setPage(nextPage)

View File

@@ -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;
}
}

View File

@@ -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<number, { text: string; color: string }> = {
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<any[]>([])
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 (
<View className="referral-page">
{/* 头部统计 */}
<View className="stats-section" style={{background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'}}>
<View className="stats-title">
<Text className="text-white text-lg font-bold"></Text>
</View>
<View className="stats-grid">
<View className="stat-item">
<Text className="stat-value text-white">{stats.totalCount}</Text>
<Text className="stat-label" style={{color: 'rgba(255,255,255,0.8)'}}></Text>
</View>
<View className="stat-item">
<Text className="stat-value text-white">{stats.pendingCount}</Text>
<Text className="stat-label" style={{color: 'rgba(255,255,255,0.8)'}}></Text>
</View>
<View className="stat-item">
<Text className="stat-value text-white">{stats.validCount}</Text>
<Text className="stat-label" style={{color: 'rgba(255,255,255,0.8)'}}></Text>
</View>
<View className="stat-item">
<Text className="stat-value text-white">¥{stats.pendingAmount}</Text>
<Text className="stat-label" style={{color: 'rgba(255,255,255,0.8)'}}></Text>
</View>
</View>
</View>
{/* 报备表单 */}
<View className="form-section">
<View className="section-title">
<Text className="font-bold text-gray-800"></Text>
</View>
<View className="form-card">
<View className="form-field">
<Text className="form-label"></Text>
<Input
className="form-input"
placeholder="请输入客户姓名"
value={formData.customerName}
onInput={(e) => handleInput('customerName', e.detail.value)}
/>
</View>
<View className="form-field">
<Text className="form-label"></Text>
<Input
className="form-input"
type="number"
maxlength={11}
placeholder="请输入客户电话"
value={formData.customerPhone}
onInput={(e) => handleInput('customerPhone', e.detail.value)}
/>
</View>
<View className="form-field">
<Text className="form-label"></Text>
<Input
className="form-input"
placeholder="请输入公司名称(选填)"
value={formData.customerCompany}
onInput={(e) => handleInput('customerCompany', e.detail.value)}
/>
</View>
<View className="form-field form-field-area">
<Text className="form-label"></Text>
<Input
className="form-input"
type="textarea"
placeholder="请描述客户需求(选填)"
value={formData.requirement}
onInput={(e) => handleInput('requirement', e.detail.value)}
style={{height: '80px', textAlign: 'left'}}
/>
</View>
<View className="submit-btn">
<Button
type="primary"
loading={submitting}
onClick={handleSubmit}
style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
border: 'none'
}}
>
</Button>
</View>
<View className="tips">
<Text className="text-gray-500 text-sm">
</Text>
</View>
</View>
</View>
{/* 推荐记录 */}
<View className="records-section">
<View className="section-title">
<Text className="font-bold text-gray-800"></Text>
</View>
{records.length === 0 ? (
<View className="empty-state">
<Text className="text-gray-400"></Text>
<Text className="text-gray-400 text-sm"></Text>
</View>
) : (
<ScrollView
scrollY
onScrollToLower={loadMore}
style={{height: '300px'}}
>
{records.map((item) => {
const statusInfo = STATUS_MAP[item.referralStatus] || STATUS_MAP[0]
return (
<View key={item.id} className="record-card">
<View className="record-header">
<View className="customer-info">
<Text className="font-bold text-gray-800">{item.customerName}</Text>
<Text
className="text-blue-500 text-sm ml-2"
onClick={() => handleCall(item.customerPhone)}
>
{item.customerPhone}
</Text>
</View>
<View
className="status-tag"
style={{backgroundColor: statusInfo.color + '20', color: statusInfo.color}}
>
{statusInfo.text}
</View>
</View>
<View className="record-body">
<View className="record-row">
<Text className="text-gray-500 text-sm"></Text>
<Text className="text-gray-700 text-sm">{item.createTime}</Text>
</View>
{parseFloat(item.referralFee) > 0 && (
<View className="record-row">
<Text className="text-gray-500 text-sm"></Text>
<Text className="text-red-500 font-bold text-sm">
¥{item.referralFee}
</Text>
</View>
)}
</View>
</View>
)
})}
{!hasMore && records.length > 0 && (
<View className="no-more">
<Text className="text-gray-400 text-sm"></Text>
</View>
)}
</ScrollView>
)}
</View>
{/* 底部安全区 */}
<View className="h-20"></View>
</View>
)
}
export default ReferralPage