Files
template-10579/src/credit/my-order/index.tsx
赵忠林 66628dbebf feat(credit): 重构信用模块订单页面并添加详情功能
- 将信用模块我的订单页面从模拟数据改为真实API数据
- 添加订单详情页面及路由配置
- 集成信用客户管理API接口调用
- 实现订单列表的搜索、分页和加载更多功能
- 添加订单状态筛选和统计信息展示
- 新增订单详情的时间线展示功能
- 更新首页进度查询跳转逻辑
- 优化页面加载和错误处理机制
2026-03-17 22:36:02 +08:00

211 lines
7.2 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 { useCallback, useEffect, useMemo, useState } from 'react'
import Taro, { useDidShow } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import { Button, ConfigProvider, Empty, Input, Loading } from '@nutui/nutui-react-taro'
import { ArrowRight, Search } from '@nutui/icons-react-taro'
import { pageCreditMpCustomer } from '@/api/credit/creditMpCustomer'
import type { CreditMpCustomer } from '@/api/credit/creditMpCustomer/model'
type ListQuery = {
page: number
limit: number
keywords?: string
userId?: number
}
const getCurrentUserId = (): number | undefined => {
try {
const raw = Taro.getStorageSync('UserId')
const n = Number(raw)
return Number.isFinite(n) && n > 0 ? n : undefined
} catch {
return undefined
}
}
const buildDesc = (row: CreditMpCustomer) => {
const price = row.price ? `${row.price}` : ''
const years = row.years ? `${row.years}` : ''
const location = [row.province, row.city, row.region].filter(Boolean).join(' ')
return [price, years, location].filter(Boolean).join(' · ')
}
const getStatusBadgeClass = (s?: string) => {
const txt = String(s || '').trim()
if (!txt) return 'bg-gray-400'
if (txt.includes('退回')) return 'bg-red-500'
if (txt.includes('完结') || txt.includes('已办结')) return 'bg-green-600'
if (txt.includes('受理') || txt.includes('通过') || txt.includes('签订')) return 'bg-orange-500'
return 'bg-blue-500'
}
export default function CreditMyOrderPage() {
const [list, setList] = useState<CreditMpCustomer[]>([])
const [count, setCount] = useState(0)
const [loading, setLoading] = useState(false)
const [loadingMore, setLoadingMore] = useState(false)
const [keywords, setKeywords] = useState('')
const [page, setPage] = useState(1)
const limit = 10
const userId = useMemo(() => getCurrentUserId(), [])
const hasMore = useMemo(() => list.length < count, [count, list.length])
const fetchPage = useCallback(
async (opts: { nextPage: number; replace: boolean }) => {
if (!userId) {
setList([])
setCount(0)
return
}
const query: ListQuery = {
page: opts.nextPage,
limit,
keywords: keywords.trim() || undefined,
userId
}
try {
if (opts.replace) setLoading(true)
else setLoadingMore(true)
const res = await pageCreditMpCustomer(query as any)
const incoming = (res?.list || []) as CreditMpCustomer[]
const total = Number(res?.count || 0)
setCount(Number.isFinite(total) ? total : 0)
setPage(opts.nextPage)
setList(prev => (opts.replace ? incoming : prev.concat(incoming)))
} catch (e) {
console.error('获取我的需求失败:', e)
Taro.showToast({ title: (e as any)?.message || '获取数据失败', icon: 'none' })
} finally {
setLoading(false)
setLoadingMore(false)
}
},
[keywords, limit, userId]
)
const reload = useCallback(async () => {
await fetchPage({ nextPage: 1, replace: true })
}, [fetchPage])
const loadMore = useCallback(async () => {
if (loading || loadingMore || !hasMore) return
await fetchPage({ nextPage: page + 1, replace: false })
}, [fetchPage, hasMore, loading, loadingMore, page])
useDidShow(() => {
reload().then()
})
useEffect(() => {
const handler = () => reload()
Taro.eventCenter.on('credit:order:created', handler)
return () => {
Taro.eventCenter.off('credit:order:created', handler)
}
}, [reload])
const goDetail = (id?: number) => {
if (!id) return
Taro.navigateTo({ url: `/credit/my-order/detail?id=${id}` })
}
return (
<View className="bg-pink-50 min-h-screen">
<ConfigProvider>
<View className="max-w-md mx-auto">
<View className="px-4 pt-3">
<View className="bg-white rounded-full border border-pink-100 px-3 py-2 flex items-center gap-2">
<Search size={16} className="text-gray-400" />
<View className="flex-1">
<Input
value={keywords}
onChange={setKeywords}
placeholder="请输入拖欠方名称查询"
onConfirm={reload}
/>
</View>
<Button size="small" type="primary" onClick={reload}>
</Button>
</View>
<View className="mt-2 text-xs text-gray-500 flex items-center justify-between">
<Text></Text>
<Text>{count}</Text>
</View>
</View>
<View className="px-4 mt-3 pb-6">
{loading ? (
<View className="bg-white rounded-xl border border-pink-100 py-10 flex items-center justify-center">
<Loading>...</Loading>
</View>
) : !list.length ? (
<View className="bg-white rounded-xl border border-pink-100 py-10">
<Empty description={userId ? '暂无需求' : '未登录或缺少用户信息'} />
</View>
) : (
list.map(row => {
const title = String(row.toUser || '-').trim()
const desc = buildDesc(row)
const statusTxt = String(row.statusTxt || '处理中').trim()
const time = String(row.createTime || '').slice(0, 19).replace('T', ' ')
return (
<View
key={String(row.id)}
className="bg-white rounded-xl border border-pink-100 p-3 mb-3"
onClick={() => goDetail(row.id)}
>
<View className="flex items-center justify-between">
<Text className="text-sm font-semibold text-gray-900">{title}</Text>
<View className="flex items-center gap-2">
<View className={`px-2 py-1 rounded-full ${getStatusBadgeClass(statusTxt)}`}>
<Text className="text-xs text-white">{statusTxt}</Text>
</View>
<ArrowRight color="#cccccc" />
</View>
</View>
{!!desc && (
<View className="mt-2 text-xs text-gray-600">
<Text>{desc}</Text>
</View>
)}
<View className="mt-2 text-xs text-gray-500 flex items-center justify-between">
<Text>ID{row.id ?? '-'}</Text>
<Text>{time || ''}</Text>
</View>
</View>
)
})
)}
{!!list.length && (
<View className="mt-2 flex justify-center">
<Button
fill="none"
size="small"
style={{ color: '#bdbdbd' }}
disabled={!hasMore || loadingMore}
onClick={loadMore}
>
{hasMore ? (loadingMore ? '加载中...' : '加载更多') : '没有更多了'}
</Button>
</View>
)}
</View>
</View>
</ConfigProvider>
</View>
)
}