feat(user): 新增收货地址管理及售后申请页面

- 新增地址类型定义,增强前端地址数据结构
- 新增地址编辑页面,支持地址智能识别和定位选点功能
- 地址编辑支持省市区选择及默认地址设置
- 新增地址列表页面,支持地址展示、删除、编辑和选择功能
- 实现售后申请页面,支持选择售后类型和退款原因
- 售后申请支持商品选择、退款金额计算和凭证上传
- 新增售后详情页面,支持售后状态展示及申请取消
- 优化页面加载和用户交互体验,增加错误提示和权限处理
This commit is contained in:
2026-07-01 12:11:56 +08:00
parent bf6ed504cc
commit 1fa58040f3
636 changed files with 58878 additions and 716 deletions

View File

@@ -0,0 +1,69 @@
import { View, ScrollView } from '@tarojs/components';
import { useState, useEffect, useRef, useCallback } from 'react';
interface VirtualListProps<T> {
data: T[];
itemHeight: number;
containerHeight: number;
renderItem: (item: T, index: number) => JSX.Element;
keyExtractor: (item: T, index: number) => string | number;
onEndReached?: () => void;
onEndReachedThreshold?: number;
}
export default function VirtualList<T>({
data,
itemHeight,
containerHeight,
renderItem,
keyExtractor,
onEndReached,
onEndReachedThreshold = 0
}: VirtualListProps<T>) {
const [startIndex, setStartIndex] = useState(0);
const [endIndex, setEndIndex] = useState(Math.ceil(containerHeight / itemHeight) + 5);
const scrollViewRef = useRef<any>(null);
const visibleItemCount = Math.ceil(containerHeight / itemHeight) + 10;
const offsetY = startIndex * itemHeight;
const handleScroll = useCallback((e: any) => {
const scrollTop = e.detail.scrollTop;
const newStartIndex = Math.floor(scrollTop / itemHeight);
const newEndIndex = newStartIndex + visibleItemCount;
setStartIndex(Math.max(0, newStartIndex - 5));
setEndIndex(Math.min(data.length, newEndIndex + 5));
// 触底加载
if (onEndReached && onEndReachedThreshold > 0) {
const { scrollHeight, clientHeight } = e.detail;
if (scrollHeight - scrollTop - clientHeight < onEndReachedThreshold) {
onEndReached();
}
}
}, [itemHeight, visibleItemCount, data.length, onEndReached, onEndReachedThreshold]);
const visibleData = data.slice(startIndex, endIndex);
return (
<ScrollView
ref={scrollViewRef}
className="w-full"
style={{ height: containerHeight }}
scrollY
onScroll={handleScroll}
scrollWithAnimation
>
<View style={{ height: data.length * itemHeight, position: 'relative' }}>
<View style={{ transform: `translateY(${offsetY}px)` }}>
{visibleData.map((item, index) => (
<View key={keyExtractor(item, startIndex + index)} style={{ height: itemHeight }}>
{renderItem(item, startIndex + index)}
</View>
))}
</View>
</View>
</ScrollView>
);
}