- 新增地址类型定义,增强前端地址数据结构 - 新增地址编辑页面,支持地址智能识别和定位选点功能 - 地址编辑支持省市区选择及默认地址设置 - 新增地址列表页面,支持地址展示、删除、编辑和选择功能 - 实现售后申请页面,支持选择售后类型和退款原因 - 售后申请支持商品选择、退款金额计算和凭证上传 - 新增售后详情页面,支持售后状态展示及申请取消 - 优化页面加载和用户交互体验,增加错误提示和权限处理
225 lines
8.0 KiB
TypeScript
225 lines
8.0 KiB
TypeScript
import { View, Text, ScrollView } from '@tarojs/components';
|
||
import { useState, useEffect, useCallback } from 'react';
|
||
import { useDidShow } from '@tarojs/taro';
|
||
import Taro from '@tarojs/taro';
|
||
import NavBar from '@/components/NavBar';
|
||
import LoadMore from '@/components/common/LoadMore';
|
||
import EmptyState from '@/components/common/EmptyState';
|
||
import { pageShopInvoiceRecord } from '@/api/shop/shopInvoiceTitle';
|
||
import type { ShopInvoiceRecord, ShopInvoiceRecordParam } from '@/api/shop/shopInvoiceTitle/model';
|
||
import type { PageResult } from '@/api';
|
||
|
||
// 发票状态映射
|
||
const statusMap = {
|
||
0: { text: '待处理', color: 'text-orange-500', bgColor: 'bg-orange-100', textColor: 'text-orange-600' },
|
||
1: { text: '开票中', color: 'text-blue-500', bgColor: 'bg-blue-100', textColor: 'text-blue-600' },
|
||
2: { text: '已完成', color: 'text-green-500', bgColor: 'bg-green-100', textColor: 'text-green-600' },
|
||
3: { text: '失败', color: 'text-red-500', bgColor: 'bg-red-100', textColor: 'text-red-600' },
|
||
};
|
||
|
||
// 发票类型映射
|
||
const invoiceTypeMap = {
|
||
normal: { label: '普通发票', bgColor: 'bg-blue-100', textColor: 'text-blue-600' },
|
||
vat: { label: '增值税发票', bgColor: 'bg-orange-100', textColor: 'text-orange-600' },
|
||
};
|
||
|
||
export default function InvoiceRecordsPage() {
|
||
const [records, setRecords] = useState<ShopInvoiceRecord[]>([]);
|
||
const [loading, setLoading] = useState(true);
|
||
const [loadingMore, setLoadingMore] = useState(false);
|
||
const [hasMore, setHasMore] = useState(true);
|
||
const [page, setPage] = useState(1);
|
||
const [activeTab, setActiveTab] = useState<'all' | 'pending' | 'completed'>('all');
|
||
|
||
// 过滤后的记录
|
||
const filteredRecords = records.filter(record => {
|
||
if (activeTab === 'all') return true;
|
||
if (activeTab === 'pending') return record.status === 0 || record.status === 1;
|
||
if (activeTab === 'completed') return record.status === 2;
|
||
return true;
|
||
});
|
||
|
||
// 加载数据
|
||
const loadData = useCallback(async (pageNum: number = 1, isLoadMore = false) => {
|
||
if (isLoadMore) {
|
||
setLoadingMore(true);
|
||
} else {
|
||
setLoading(true);
|
||
}
|
||
|
||
try {
|
||
const params: ShopInvoiceRecordParam = {
|
||
page: pageNum,
|
||
limit: 10,
|
||
order: 'desc',
|
||
sort: 'createTime',
|
||
};
|
||
|
||
const result: PageResult<ShopInvoiceRecord> = await pageShopInvoiceRecord(params);
|
||
|
||
if (result && result.list) {
|
||
if (isLoadMore) {
|
||
setRecords(prev => [...prev, ...result.list]);
|
||
} else {
|
||
setRecords(result.list);
|
||
}
|
||
setHasMore(result.list.length >= 10);
|
||
setPage(pageNum);
|
||
}
|
||
} catch (e: any) {
|
||
console.error('获取发票记录失败:', e);
|
||
Taro.showToast({ title: e.message || '获取数据失败', icon: 'none' });
|
||
} finally {
|
||
setLoading(false);
|
||
setLoadingMore(false);
|
||
}
|
||
}, []);
|
||
|
||
// 下拉刷新
|
||
const onPullDownRefresh = useCallback(() => {
|
||
loadData(1).then(() => {
|
||
Taro.stopPullDownRefresh();
|
||
});
|
||
}, [loadData]);
|
||
|
||
// 上拉加载更多
|
||
const onReachBottom = useCallback(() => {
|
||
if (!loadingMore && hasMore) {
|
||
loadData(page + 1, true);
|
||
}
|
||
}, [loadingMore, hasMore, page, loadData]);
|
||
|
||
useEffect(() => {
|
||
loadData(1);
|
||
}, []);
|
||
|
||
// 格式化金额
|
||
const formatMoney = (money: number | undefined) => {
|
||
return `¥${(money || 0).toFixed(2)}`;
|
||
};
|
||
|
||
// 格式化时间
|
||
const formatDate = (dateStr: string | undefined) => {
|
||
if (!dateStr) return '';
|
||
const date = new Date(dateStr);
|
||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||
const day = String(date.getDate()).padStart(2, '0');
|
||
const hour = String(date.getHours()).padStart(2, '0');
|
||
const minute = String(date.getMinutes()).padStart(2, '0');
|
||
return `${date.getFullYear()}-${month}-${day} ${hour}:${minute}`;
|
||
};
|
||
|
||
// 获取状态信息
|
||
const getStatusInfo = (status: number | undefined) => {
|
||
return statusMap[status as keyof typeof statusMap] || statusMap[0];
|
||
};
|
||
|
||
// 获取类型信息
|
||
const getTypeInfo = (invoiceType: string | undefined) => {
|
||
return invoiceTypeMap[invoiceType as keyof typeof invoiceTypeMap] || invoiceTypeMap.normal;
|
||
};
|
||
|
||
// 查看发票
|
||
const handleViewInvoice = (record: ShopInvoiceRecord) => {
|
||
if (record.invoiceUrl) {
|
||
Taro.previewImage({ urls: [record.invoiceUrl] });
|
||
} else {
|
||
Taro.showToast({ title: '暂无发票', icon: 'none' });
|
||
}
|
||
};
|
||
|
||
return (
|
||
<View className="min-h-screen bg-gray-100">
|
||
<NavBar title="发票记录" />
|
||
|
||
{/* 标签切换 */}
|
||
<View className="bg-white flex">
|
||
{[
|
||
{ key: 'all', label: '全部' },
|
||
{ key: 'pending', label: '待处理' },
|
||
{ key: 'completed', label: '已完成' }
|
||
].map(tab => (
|
||
<View
|
||
key={tab.key}
|
||
className={`flex-1 py-3 text-center relative ${
|
||
activeTab === tab.key ? 'text-red-500 font-medium' : 'text-gray-500'
|
||
}`}
|
||
onClick={() => setActiveTab(tab.key as any)}
|
||
>
|
||
<Text>{tab.label}</Text>
|
||
{activeTab === tab.key && (
|
||
<View className="absolute bottom-0 w-8 bg-red-500 rounded-t" style={{ left: '50%', transform: 'translateX(-50%)', height: '4px' }} />
|
||
)}
|
||
</View>
|
||
))}
|
||
</View>
|
||
|
||
{/* 记录列表 */}
|
||
{loading ? (
|
||
<LoadMore loading />
|
||
) : filteredRecords.length === 0 ? (
|
||
<EmptyState message="暂无发票记录" />
|
||
) : (
|
||
<ScrollView
|
||
scrollY
|
||
className="p-4"
|
||
onScrollToLower={onReachBottom}
|
||
onScrollUpperThreshold={50}
|
||
>
|
||
{filteredRecords.map(record => {
|
||
const statusInfo = getStatusInfo(record.status);
|
||
const typeInfo = getTypeInfo(record.invoiceType);
|
||
|
||
return (
|
||
<View key={record.id} className="bg-white rounded-lg p-4 mb-4">
|
||
<View className="flex justify-between items-start mb-3">
|
||
<View className="flex-1">
|
||
<View className="flex items-center mb-2">
|
||
<Text className={`text-xs px-2 py-1 rounded mr-2 ${typeInfo.bgColor} ${typeInfo.textColor}`}>
|
||
{typeInfo.label}
|
||
</Text>
|
||
<Text className={`text-xs px-2 py-1 rounded mr-2 ${statusInfo.bgColor} ${statusInfo.textColor}`}>
|
||
{statusInfo.text}
|
||
</Text>
|
||
</View>
|
||
<Text className="text-lg font-medium">{record.titleName}</Text>
|
||
{record.orderNo && (
|
||
<Text className="text-xs text-gray-500 mt-1 block">订单号:{record.orderNo}</Text>
|
||
)}
|
||
</View>
|
||
<Text className="text-xl font-bold text-red-500">{formatMoney(record.amount)}</Text>
|
||
</View>
|
||
|
||
<View className="text-sm text-gray-500 flex flex-col" style={{ gap: '4px' }}>
|
||
<View className="flex justify-between">
|
||
<Text>申请时间</Text>
|
||
<Text>{formatDate(record.applyTime)}</Text>
|
||
</View>
|
||
{record.invoiceNo && (
|
||
<View className="flex justify-between">
|
||
<Text>发票号</Text>
|
||
<Text>{record.invoiceNo}</Text>
|
||
</View>
|
||
)}
|
||
</View>
|
||
|
||
{record.status === 2 && (
|
||
<View className="mt-3 pt-3 border-t border-gray-100 flex justify-end">
|
||
<View
|
||
className="px-4 py-2 bg-blue-500 text-white rounded-full"
|
||
onClick={() => handleViewInvoice(record)}
|
||
>
|
||
<Text className="text-sm">查看发票</Text>
|
||
</View>
|
||
</View>
|
||
)}
|
||
</View>
|
||
);
|
||
})}
|
||
<LoadMore loading={loadingMore} hasMore={hasMore} />
|
||
</ScrollView>
|
||
)}
|
||
</View>
|
||
);
|
||
}
|