|
|
@@ -1,6 +1,7 @@
|
|
|
-import React, { useState, useEffect } from 'react'
|
|
|
+import React, { useState } from 'react'
|
|
|
import { View, Text, ScrollView, Input, Button } from '@tarojs/components'
|
|
|
import Taro from '@tarojs/taro'
|
|
|
+import { useInfiniteQuery } from '@tanstack/react-query'
|
|
|
import { YongrenTabBarLayout } from '@d8d/yongren-shared-ui/components/YongrenTabBarLayout'
|
|
|
import { Navbar } from '@d8d/mini-shared-ui-components/components/navbar'
|
|
|
import { enterpriseOrderClient } from '../../api'
|
|
|
@@ -10,93 +11,45 @@ type OrderStatus = 'all' | 'in_progress' | 'completed' | 'cancelled'
|
|
|
const OrderList: React.FC = () => {
|
|
|
const [activeStatus, setActiveStatus] = useState<OrderStatus>('all')
|
|
|
const [searchKeyword, setSearchKeyword] = useState('')
|
|
|
- const [orders, setOrders] = useState<any[]>([])
|
|
|
- const [loading, setLoading] = useState(false)
|
|
|
- const [error, setError] = useState<string | null>(null)
|
|
|
- const [pagination, setPagination] = useState({
|
|
|
- page: 1,
|
|
|
- pageSize: 20,
|
|
|
- total: 0,
|
|
|
- totalPages: 1
|
|
|
- })
|
|
|
const [sortBy, setSortBy] = useState('createTime')
|
|
|
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc')
|
|
|
|
|
|
- // 初始加载订单数据
|
|
|
- useEffect(() => {
|
|
|
- fetchOrders(1, '', 'all')
|
|
|
- }, [])
|
|
|
-
|
|
|
- const statusFilters = [
|
|
|
- { key: 'all', label: '全部订单', activeClass: 'bg-blue-100 text-blue-800', inactiveClass: 'bg-gray-100 text-gray-800' },
|
|
|
- { key: 'in_progress', label: '进行中', activeClass: 'bg-green-100 text-green-800', inactiveClass: 'bg-gray-100 text-gray-800' },
|
|
|
- { key: 'completed', label: '已完成', activeClass: 'bg-blue-100 text-blue-800', inactiveClass: 'bg-gray-100 text-gray-800' },
|
|
|
- { key: 'cancelled', label: '已取消', activeClass: 'bg-gray-100 text-gray-800', inactiveClass: 'bg-gray-100 text-gray-800' },
|
|
|
- ]
|
|
|
-
|
|
|
- // 状态标签和样式辅助函数
|
|
|
- const getStatusLabel = (status: string) => {
|
|
|
- switch (status) {
|
|
|
- case 'draft': return '草稿'
|
|
|
- case 'confirmed': return '已确认'
|
|
|
- case 'in_progress': return '进行中'
|
|
|
- case 'completed': return '已完成'
|
|
|
- case 'cancelled': return '已取消'
|
|
|
- default: return status
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- const getStatusClass = (status: string) => {
|
|
|
- switch (status) {
|
|
|
- case 'draft': return 'bg-gray-100 text-gray-800'
|
|
|
- case 'confirmed': return 'bg-blue-100 text-blue-800'
|
|
|
- case 'in_progress': return 'bg-green-100 text-green-800'
|
|
|
- case 'completed': return 'bg-purple-100 text-purple-800'
|
|
|
- case 'cancelled': return 'bg-red-100 text-red-800'
|
|
|
- default: return 'bg-gray-100 text-gray-800'
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 获取企业用户信息
|
|
|
- const getEnterpriseUserInfo = () => {
|
|
|
- try {
|
|
|
- const userInfo = Taro.getStorageSync('enterpriseUserInfo')
|
|
|
- return userInfo || null
|
|
|
- } catch (error) {
|
|
|
- console.error('获取企业用户信息失败:', error)
|
|
|
- return null
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 获取订单列表
|
|
|
- const fetchOrders = async (page = 1, search = '', status = 'all') => {
|
|
|
- setLoading(true)
|
|
|
- setError(null)
|
|
|
- try {
|
|
|
+ // 使用useInfiniteQuery进行无限滚动分页
|
|
|
+ const {
|
|
|
+ data,
|
|
|
+ isLoading,
|
|
|
+ error,
|
|
|
+ isFetchingNextPage,
|
|
|
+ fetchNextPage,
|
|
|
+ hasNextPage,
|
|
|
+ refetch
|
|
|
+ } = useInfiniteQuery({
|
|
|
+ queryKey: ['company-orders', activeStatus, searchKeyword, sortBy, sortOrder],
|
|
|
+ queryFn: async ({ pageParam = 1 }) => {
|
|
|
const userInfo = getEnterpriseUserInfo()
|
|
|
const companyId = userInfo?.companyId || 0
|
|
|
|
|
|
// 构建查询参数
|
|
|
const queryParams: any = {
|
|
|
- page,
|
|
|
- pageSize: pagination.pageSize,
|
|
|
+ page: pageParam,
|
|
|
+ pageSize: 20,
|
|
|
sortBy,
|
|
|
sortOrder: sortOrder.toUpperCase()
|
|
|
}
|
|
|
|
|
|
- // 添加公司ID(API可能需要)
|
|
|
+ // 添加公司ID
|
|
|
if (companyId) {
|
|
|
queryParams.companyId = companyId
|
|
|
}
|
|
|
|
|
|
// 搜索关键词
|
|
|
- if (search) {
|
|
|
- queryParams.orderName = search
|
|
|
+ if (searchKeyword) {
|
|
|
+ queryParams.orderName = searchKeyword
|
|
|
}
|
|
|
|
|
|
// 状态筛选(排除'all'状态)
|
|
|
- if (status !== 'all') {
|
|
|
- queryParams.orderStatus = status
|
|
|
+ if (activeStatus !== 'all') {
|
|
|
+ queryParams.orderStatus = activeStatus
|
|
|
}
|
|
|
|
|
|
// 调用企业专用订单API
|
|
|
@@ -119,46 +72,88 @@ const OrderList: React.FC = () => {
|
|
|
actualPeople: order.actualPersonCount || 0,
|
|
|
startDate: order.expectedStartDate ? new Date(order.expectedStartDate).toISOString().split('T')[0] : '未设置',
|
|
|
endDate: order.expectedEndDate ? new Date(order.expectedEndDate).toISOString().split('T')[0] : '未设置',
|
|
|
- // 新增字段:人才姓名和岗位
|
|
|
talentName: order.talentName || '待关联',
|
|
|
position: order.position || '未设置',
|
|
|
- // 统计字段 - 需要后续通过单独的API获取
|
|
|
checkinStats: { current: 0, total: order.actualPersonCount || 0, percentage: 0 },
|
|
|
salaryVideoStats: { current: 0, total: order.actualPersonCount || 0, percentage: 0 },
|
|
|
taxVideoStats: { current: 0, total: order.actualPersonCount || 0, percentage: 0 }
|
|
|
}))
|
|
|
- setOrders(transformedOrders)
|
|
|
- setPagination({
|
|
|
- page: data.meta?.page || page,
|
|
|
- pageSize: data.meta?.pageSize || pagination.pageSize,
|
|
|
- total: data.meta?.total || 0,
|
|
|
- totalPages: data.meta?.totalPages || 1
|
|
|
- })
|
|
|
+
|
|
|
+ return {
|
|
|
+ data: transformedOrders,
|
|
|
+ meta: data.meta || { page: pageParam, pageSize: 20, total: 0, totalPages: 1 }
|
|
|
+ }
|
|
|
} else {
|
|
|
throw new Error(`获取订单列表失败: ${response.status}`)
|
|
|
}
|
|
|
- } catch (err) {
|
|
|
- console.error('获取订单列表错误:', err)
|
|
|
- setError(err instanceof Error ? err.message : '获取订单列表失败')
|
|
|
- Taro.showToast({
|
|
|
- title: '获取订单列表失败',
|
|
|
- icon: 'none'
|
|
|
- })
|
|
|
- } finally {
|
|
|
- setLoading(false)
|
|
|
+ },
|
|
|
+ getNextPageParam: (lastPage, allPages) => {
|
|
|
+ const totalPages = lastPage.meta?.totalPages || 1
|
|
|
+ const currentPage = lastPage.meta?.page || allPages.length
|
|
|
+ return currentPage < totalPages ? currentPage + 1 : undefined
|
|
|
+ },
|
|
|
+ initialPageParam: 1,
|
|
|
+ staleTime: 5 * 60 * 1000, // 5分钟
|
|
|
+ })
|
|
|
+
|
|
|
+ const statusFilters = [
|
|
|
+ { key: 'all', label: '全部订单', activeClass: 'bg-blue-100 text-blue-800', inactiveClass: 'bg-gray-100 text-gray-800' },
|
|
|
+ { key: 'in_progress', label: '进行中', activeClass: 'bg-green-100 text-green-800', inactiveClass: 'bg-gray-100 text-gray-800' },
|
|
|
+ { key: 'completed', label: '已完成', activeClass: 'bg-blue-100 text-blue-800', inactiveClass: 'bg-gray-100 text-gray-800' },
|
|
|
+ { key: 'cancelled', label: '已取消', activeClass: 'bg-gray-100 text-gray-800', inactiveClass: 'bg-gray-100 text-gray-800' },
|
|
|
+ ]
|
|
|
+
|
|
|
+ // 状态标签和样式辅助函数
|
|
|
+ const getStatusLabel = (status: string) => {
|
|
|
+ switch (status) {
|
|
|
+ case 'draft': return '草稿'
|
|
|
+ case 'confirmed': return '已确认'
|
|
|
+ case 'in_progress': return '进行中'
|
|
|
+ case 'completed': return '已完成'
|
|
|
+ case 'cancelled': return '已取消'
|
|
|
+ default: return status
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const getStatusClass = (status: string) => {
|
|
|
+ switch (status) {
|
|
|
+ case 'draft': return 'bg-gray-100 text-gray-800'
|
|
|
+ case 'confirmed': return 'bg-blue-100 text-blue-800'
|
|
|
+ case 'in_progress': return 'bg-green-100 text-green-800'
|
|
|
+ case 'completed': return 'bg-purple-100 text-purple-800'
|
|
|
+ case 'cancelled': return 'bg-red-100 text-red-800'
|
|
|
+ default: return 'bg-gray-100 text-gray-800'
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取企业用户信息
|
|
|
+ const getEnterpriseUserInfo = () => {
|
|
|
+ try {
|
|
|
+ const userInfo = Taro.getStorageSync('enterpriseUserInfo')
|
|
|
+ return userInfo || null
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取企业用户信息失败:', error)
|
|
|
+ return null
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
const handleSearch = () => {
|
|
|
console.log('搜索关键词:', searchKeyword)
|
|
|
- // 搜索订单
|
|
|
- fetchOrders(1, searchKeyword, activeStatus)
|
|
|
+ // 使用refetch重新获取数据,queryKey变化会自动触发重新查询
|
|
|
+ refetch()
|
|
|
}
|
|
|
|
|
|
const handleStatusFilter = (status: OrderStatus) => {
|
|
|
setActiveStatus(status)
|
|
|
- // 根据状态筛选订单
|
|
|
- fetchOrders(1, searchKeyword, status)
|
|
|
+ // queryKey变化会自动触发重新查询
|
|
|
+ }
|
|
|
+
|
|
|
+ // 触底加载更多
|
|
|
+ const handleScrollToLower = () => {
|
|
|
+ if (hasNextPage && !isFetchingNextPage) {
|
|
|
+ fetchNextPage()
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
const handleViewDetail = (orderId: number) => {
|
|
|
@@ -179,25 +174,16 @@ const OrderList: React.FC = () => {
|
|
|
// TODO: 跳转到创建订单页
|
|
|
}
|
|
|
|
|
|
- const handlePrevPage = () => {
|
|
|
- if (pagination.page > 1) {
|
|
|
- const newPage = pagination.page - 1
|
|
|
- fetchOrders(newPage, searchKeyword, activeStatus)
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- const handleNextPage = () => {
|
|
|
- if (pagination.page < pagination.totalPages) {
|
|
|
- const newPage = pagination.page + 1
|
|
|
- fetchOrders(newPage, searchKeyword, activeStatus)
|
|
|
- }
|
|
|
- }
|
|
|
+ // 合并所有分页数据
|
|
|
+ const allOrders = data?.pages.flatMap(page => page.data) || []
|
|
|
|
|
|
return (
|
|
|
<YongrenTabBarLayout activeKey="order">
|
|
|
<ScrollView
|
|
|
className="h-[calc(100%-60px)] overflow-y-auto px-4 pb-4 pt-0"
|
|
|
scrollY
|
|
|
+ onScrollToLower={handleScrollToLower}
|
|
|
>
|
|
|
{/* 导航栏 */}
|
|
|
<Navbar
|
|
|
@@ -228,71 +214,61 @@ const OrderList: React.FC = () => {
|
|
|
</View>
|
|
|
</View>
|
|
|
|
|
|
+ {/* 搜索栏 */}
|
|
|
+ <View className="p-4 border-b border-gray-200">
|
|
|
+ <Input
|
|
|
+ className="border border-gray-300 rounded-lg px-3 py-2 text-sm w-full"
|
|
|
+ placeholder="按订单号、人才姓名搜索"
|
|
|
+ value={searchKeyword}
|
|
|
+ onInput={(e) => setSearchKeyword(e.detail.value)}
|
|
|
+ />
|
|
|
+ <Button
|
|
|
+ className="bg-gray-100 text-gray-800 text-xs px-3 py-2 rounded-lg mt-2 w-full"
|
|
|
+ onClick={handleSearch}
|
|
|
+ >
|
|
|
+ <Text className="i-heroicons-magnifying-glass-20-solid mr-1" />
|
|
|
+ 搜索
|
|
|
+ </Button>
|
|
|
+ </View>
|
|
|
+
|
|
|
{/* 订单列表区域 */}
|
|
|
<View className="p-4">
|
|
|
{/* 标题和新建订单按钮 */}
|
|
|
<View className="flex justify-between items-center mb-4">
|
|
|
<Text className="font-semibold text-gray-700">订单列表</Text>
|
|
|
- <View className="flex items-center space-x-2">
|
|
|
- <Button
|
|
|
- className="bg-gray-100 text-gray-700 text-xs px-2 py-1 rounded-lg"
|
|
|
- onClick={() => {
|
|
|
- const newSortOrder = sortOrder === 'desc' ? 'asc' : 'desc'
|
|
|
- setSortOrder(newSortOrder)
|
|
|
- fetchOrders(1, searchKeyword, activeStatus)
|
|
|
- }}
|
|
|
- >
|
|
|
- <Text>{sortOrder === 'desc' ? '↓ 最新' : '↑ 最早'}</Text>
|
|
|
- </Button>
|
|
|
- <Button
|
|
|
+ <Button
|
|
|
className="bg-blue-500 text-white text-xs px-3 py-1 rounded-lg"
|
|
|
onClick={handleCreateOrder}
|
|
|
>
|
|
|
- <Text className="fas fa-plus mr-1">+</Text>
|
|
|
+ <Text className="i-heroicons-plus-20-solid mr-1" />
|
|
|
<Text>新建订单</Text>
|
|
|
</Button>
|
|
|
- </View>
|
|
|
</View>
|
|
|
|
|
|
- {/* 搜索栏 */}
|
|
|
- <View className="mb-4">
|
|
|
- <Input
|
|
|
- className="border border-gray-300 rounded-lg px-3 py-2 text-sm"
|
|
|
- placeholder="按订单号、人才姓名搜索"
|
|
|
- value={searchKeyword}
|
|
|
- onInput={(e) => setSearchKeyword(e.detail.value)}
|
|
|
- />
|
|
|
- <Button
|
|
|
- className="bg-gray-100 text-gray-800 text-xs px-3 py-2 rounded-lg mt-2"
|
|
|
- onClick={handleSearch}
|
|
|
- >
|
|
|
- 搜索
|
|
|
- </Button>
|
|
|
- </View>
|
|
|
|
|
|
{/* 订单卡片列表 */}
|
|
|
- {loading && (
|
|
|
+ {isLoading && (
|
|
|
<View className="flex justify-center items-center py-8">
|
|
|
<Text className="text-gray-500">加载中...</Text>
|
|
|
</View>
|
|
|
)}
|
|
|
{error && (
|
|
|
<View className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
|
|
|
- <Text className="text-red-700">{error}</Text>
|
|
|
+ <Text className="text-red-700">{error.message || '获取订单列表失败'}</Text>
|
|
|
</View>
|
|
|
)}
|
|
|
- {!loading && !error && (
|
|
|
+ {!isLoading && !error && (
|
|
|
<View className="space-y-4">
|
|
|
- {orders.length === 0 ? (
|
|
|
+ {allOrders.length === 0 ? (
|
|
|
<View className="flex justify-center items-center py-8">
|
|
|
<Text className="text-gray-500">暂无订单数据</Text>
|
|
|
</View>
|
|
|
) : (
|
|
|
- orders.map((order) => (
|
|
|
+ allOrders.map((order) => (
|
|
|
<View key={order.id} className="card bg-white p-4">
|
|
|
{/* 订单头部 */}
|
|
|
<View className="flex justify-between items-start mb-3">
|
|
|
- <View>
|
|
|
+ <View className="flex flex-col">
|
|
|
<Text className="font-semibold text-gray-800">{order.name}</Text>
|
|
|
<Text className="text-xs text-gray-500">{order.createdAt} 创建</Text>
|
|
|
</View>
|
|
|
@@ -303,27 +279,27 @@ const OrderList: React.FC = () => {
|
|
|
|
|
|
{/* 订单信息网格 */}
|
|
|
<View className="grid grid-cols-2 gap-3 text-sm mb-3">
|
|
|
- <View>
|
|
|
+ <View className="flex flex-col">
|
|
|
<Text className="text-gray-500">预计人数</Text>
|
|
|
<Text className="text-gray-800">{order.expectedPeople}人</Text>
|
|
|
</View>
|
|
|
- <View>
|
|
|
+ <View className="flex flex-col">
|
|
|
<Text className="text-gray-500">实际人数</Text>
|
|
|
<Text className="text-gray-800">{order.actualPeople}人</Text>
|
|
|
</View>
|
|
|
- <View>
|
|
|
+ <View className="flex flex-col">
|
|
|
<Text className="text-gray-500">开始日期</Text>
|
|
|
<Text className="text-gray-800">{order.startDate}</Text>
|
|
|
</View>
|
|
|
- <View>
|
|
|
+ <View className="flex flex-col">
|
|
|
<Text className="text-gray-500">预计结束</Text>
|
|
|
<Text className="text-gray-800">{order.endDate}</Text>
|
|
|
</View>
|
|
|
- <View>
|
|
|
+ <View className="flex flex-col">
|
|
|
<Text className="text-gray-500">人才姓名</Text>
|
|
|
<Text className="text-gray-800">{order.talentName}</Text>
|
|
|
</View>
|
|
|
- <View>
|
|
|
+ <View className="flex flex-col">
|
|
|
<Text className="text-gray-500">岗位</Text>
|
|
|
<Text className="text-gray-800">{order.position}</Text>
|
|
|
</View>
|
|
|
@@ -331,21 +307,21 @@ const OrderList: React.FC = () => {
|
|
|
|
|
|
{/* 打卡数据统计网格 */}
|
|
|
<View className="grid grid-cols-3 gap-2 mb-3">
|
|
|
- <View className="bg-blue-50 rounded-lg p-2 text-center">
|
|
|
+ <View className="bg-blue-50 rounded-lg p-2 text-center flex flex-col">
|
|
|
<Text className="text-xs text-gray-600">本月打卡</Text>
|
|
|
<Text className="text-sm font-bold text-gray-800">
|
|
|
{order.checkinStats.current}/{order.checkinStats.total}
|
|
|
</Text>
|
|
|
<Text className="text-xs text-gray-500">{order.checkinStats.percentage}%</Text>
|
|
|
</View>
|
|
|
- <View className="bg-green-50 rounded-lg p-2 text-center">
|
|
|
+ <View className="bg-green-50 rounded-lg p-2 text-center flex flex-col">
|
|
|
<Text className="text-xs text-gray-600">工资视频</Text>
|
|
|
<Text className="text-sm font-bold text-gray-800">
|
|
|
{order.salaryVideoStats.current}/{order.salaryVideoStats.total}
|
|
|
</Text>
|
|
|
<Text className="text-xs text-gray-500">{order.salaryVideoStats.percentage}%</Text>
|
|
|
</View>
|
|
|
- <View className="bg-purple-50 rounded-lg p-2 text-center">
|
|
|
+ <View className="bg-purple-50 rounded-lg p-2 text-center flex flex-col">
|
|
|
<Text className="text-xs text-gray-600">个税视频</Text>
|
|
|
<Text className="text-sm font-bold text-gray-800">
|
|
|
{order.taxVideoStats.current}/{order.taxVideoStats.total}
|
|
|
@@ -360,42 +336,35 @@ const OrderList: React.FC = () => {
|
|
|
className="text-blue-500"
|
|
|
onClick={() => handleViewDetail(order.id)}
|
|
|
>
|
|
|
- <Text className="fas fa-eye mr-1">👁️</Text>
|
|
|
+ <Text className="i-heroicons-eye-20-solid mr-1" />
|
|
|
<Text>查看详情</Text>
|
|
|
</Button>
|
|
|
<Button
|
|
|
className="text-gray-500"
|
|
|
onClick={() => handleDownloadVideo(order.id)}
|
|
|
>
|
|
|
- <Text className="fas fa-download mr-1">⬇️</Text>
|
|
|
+ <Text className="i-heroicons-arrow-down-tray-20-solid mr-1" />
|
|
|
<Text>下载视频</Text>
|
|
|
</Button>
|
|
|
</View>
|
|
|
</View>
|
|
|
))
|
|
|
)}
|
|
|
+
|
|
|
+ {isFetchingNextPage && (
|
|
|
+ <View className="flex justify-center py-4">
|
|
|
+ <Text className="i-heroicons-arrow-path-20-solid animate-spin w-6 h-6 text-blue-500 mr-2" />
|
|
|
+ <Text className="text-sm text-gray-500">加载更多...</Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {!hasNextPage && allOrders.length > 0 && (
|
|
|
+ <View className="text-center py-4">
|
|
|
+ <Text className="text-sm text-gray-400">没有更多了</Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
</View>
|
|
|
)}
|
|
|
- {/* 分页控件 */}
|
|
|
- <View className="flex justify-center items-center mt-6">
|
|
|
- <Button
|
|
|
- className="text-gray-500 text-sm px-3 py-1"
|
|
|
- onClick={handlePrevPage}
|
|
|
- disabled={pagination.page <= 1}
|
|
|
- >
|
|
|
- 上一页
|
|
|
- </Button>
|
|
|
- <Text className="mx-4 text-sm">
|
|
|
- {pagination.page} / {pagination.totalPages}
|
|
|
- </Text>
|
|
|
- <Button
|
|
|
- className="text-gray-500 text-sm px-3 py-1"
|
|
|
- onClick={handleNextPage}
|
|
|
- disabled={pagination.page >= pagination.totalPages}
|
|
|
- >
|
|
|
- 下一页
|
|
|
- </Button>
|
|
|
- </View>
|
|
|
</View>
|
|
|
</ScrollView>
|
|
|
</YongrenTabBarLayout>
|