|
@@ -1,7 +1,7 @@
|
|
|
import React, { useEffect, useState } from 'react'
|
|
import React, { useEffect, useState } from 'react'
|
|
|
import { View, Text, Input, ScrollView } from '@tarojs/components'
|
|
import { View, Text, Input, ScrollView } from '@tarojs/components'
|
|
|
-import Taro from '@tarojs/taro'
|
|
|
|
|
-import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
|
|
|
|
|
+import Taro, { useDidShow } from '@tarojs/taro'
|
|
|
|
|
+import { useInfiniteQuery } from '@tanstack/react-query'
|
|
|
import dayjs from 'dayjs'
|
|
import dayjs from 'dayjs'
|
|
|
import { YongrenTabBarLayout } from '@d8d/yongren-shared-ui/components/YongrenTabBarLayout'
|
|
import { YongrenTabBarLayout } from '@d8d/yongren-shared-ui/components/YongrenTabBarLayout'
|
|
|
import { PageContainer } from '@d8d/mini-shared-ui-components/components/page-container'
|
|
import { PageContainer } from '@d8d/mini-shared-ui-components/components/page-container'
|
|
@@ -15,7 +15,7 @@ export interface TalentManagementProps {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 企业专用人才列表项类型 - 匹配CompanyPersonListItemSchema
|
|
// 企业专用人才列表项类型 - 匹配CompanyPersonListItemSchema
|
|
|
-interface CompanyPersonListItem {
|
|
|
|
|
|
|
+interface _CompanyPersonListItem {
|
|
|
personId: number
|
|
personId: number
|
|
|
name: string
|
|
name: string
|
|
|
gender: string
|
|
gender: string
|
|
@@ -41,7 +41,7 @@ const CHINESE_STATUS_TO_ENUM: Record<string, WorkStatus> = {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// WorkStatus枚举到中文状态的映射
|
|
// WorkStatus枚举到中文状态的映射
|
|
|
-const ENUM_TO_CHINESE_STATUS: Record<WorkStatus, string> = {
|
|
|
|
|
|
|
+const _ENUM_TO_CHINESE_STATUS: Record<WorkStatus, string> = {
|
|
|
[WorkStatus.WORKING]: '在职',
|
|
[WorkStatus.WORKING]: '在职',
|
|
|
[WorkStatus.PRE_WORKING]: '待入职',
|
|
[WorkStatus.PRE_WORKING]: '待入职',
|
|
|
[WorkStatus.RESIGNED]: '离职',
|
|
[WorkStatus.RESIGNED]: '离职',
|
|
@@ -52,38 +52,43 @@ const ENUM_TO_CHINESE_STATUS: Record<WorkStatus, string> = {
|
|
|
const TalentManagement: React.FC<TalentManagementProps> = () => {
|
|
const TalentManagement: React.FC<TalentManagementProps> = () => {
|
|
|
const { user: _user } = useAuth()
|
|
const { user: _user } = useAuth()
|
|
|
const { isLoggedIn } = useRequireAuth()
|
|
const { isLoggedIn } = useRequireAuth()
|
|
|
- const queryClient = useQueryClient()
|
|
|
|
|
|
|
|
|
|
// 搜索和筛选状态
|
|
// 搜索和筛选状态
|
|
|
const [searchText, setSearchText] = useState('')
|
|
const [searchText, setSearchText] = useState('')
|
|
|
const [activeStatus, setActiveStatus] = useState<'全部' | '在职' | '待入职' | '离职'>('全部')
|
|
const [activeStatus, setActiveStatus] = useState<'全部' | '在职' | '待入职' | '离职'>('全部')
|
|
|
const [activeDisabilityType, setActiveDisabilityType] = useState<string>('')
|
|
const [activeDisabilityType, setActiveDisabilityType] = useState<string>('')
|
|
|
- const [page, setPage] = useState(1)
|
|
|
|
|
- const limit = 20
|
|
|
|
|
|
|
+ const [refreshing, setRefreshing] = useState(false)
|
|
|
|
|
|
|
|
// 搜索参数防抖
|
|
// 搜索参数防抖
|
|
|
const [debouncedSearchText, setDebouncedSearchText] = useState('')
|
|
const [debouncedSearchText, setDebouncedSearchText] = useState('')
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
const timer = setTimeout(() => {
|
|
const timer = setTimeout(() => {
|
|
|
setDebouncedSearchText(searchText)
|
|
setDebouncedSearchText(searchText)
|
|
|
- setPage(1) // 搜索时重置到第一页
|
|
|
|
|
}, 500)
|
|
}, 500)
|
|
|
return () => clearTimeout(timer)
|
|
return () => clearTimeout(timer)
|
|
|
}, [searchText])
|
|
}, [searchText])
|
|
|
|
|
|
|
|
- // 构建查询参数(企业专用API使用camelCase参数名)
|
|
|
|
|
- const queryParams = {
|
|
|
|
|
- search: debouncedSearchText || undefined,
|
|
|
|
|
- jobStatus: activeStatus !== '全部' ? CHINESE_STATUS_TO_ENUM[activeStatus] : undefined,
|
|
|
|
|
- disabilityType: activeDisabilityType || undefined,
|
|
|
|
|
- page,
|
|
|
|
|
- limit
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 使用 useInfiniteQuery 进行无限滚动分页
|
|
|
|
|
+ const {
|
|
|
|
|
+ data,
|
|
|
|
|
+ isLoading,
|
|
|
|
|
+ error,
|
|
|
|
|
+ isFetchingNextPage,
|
|
|
|
|
+ fetchNextPage,
|
|
|
|
|
+ hasNextPage,
|
|
|
|
|
+ refetch
|
|
|
|
|
+ } = useInfiniteQuery({
|
|
|
|
|
+ queryKey: ['talentList', { search: debouncedSearchText, jobStatus: activeStatus !== '全部' ? CHINESE_STATUS_TO_ENUM[activeStatus] : undefined, disabilityType: activeDisabilityType || undefined }],
|
|
|
|
|
+ queryFn: async ({ pageParam = 1 }) => {
|
|
|
|
|
+ // 构建查询参数(企业专用API使用camelCase参数名)
|
|
|
|
|
+ const queryParams = {
|
|
|
|
|
+ search: debouncedSearchText || undefined,
|
|
|
|
|
+ jobStatus: activeStatus !== '全部' ? CHINESE_STATUS_TO_ENUM[activeStatus] : undefined,
|
|
|
|
|
+ disabilityType: activeDisabilityType || undefined,
|
|
|
|
|
+ page: pageParam,
|
|
|
|
|
+ limit: 20
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 获取人才列表数据(使用企业专用API)
|
|
|
|
|
- const { data: talentList, isLoading, error, refetch } = useQuery({
|
|
|
|
|
- queryKey: ['talentList', queryParams],
|
|
|
|
|
- queryFn: async () => {
|
|
|
|
|
const response = await enterpriseDisabilityClient.index.$get({
|
|
const response = await enterpriseDisabilityClient.index.$get({
|
|
|
query: queryParams
|
|
query: queryParams
|
|
|
})
|
|
})
|
|
@@ -103,19 +108,28 @@ const TalentManagement: React.FC<TalentManagementProps> = () => {
|
|
|
totalPages: pagination.totalPages
|
|
totalPages: pagination.totalPages
|
|
|
}
|
|
}
|
|
|
},
|
|
},
|
|
|
|
|
+ getNextPageParam: (lastPage, allPages) => {
|
|
|
|
|
+ const totalPages = lastPage.totalPages || 1
|
|
|
|
|
+ const currentPage = lastPage.page || allPages.length
|
|
|
|
|
+ return currentPage < totalPages ? currentPage + 1 : undefined
|
|
|
|
|
+ },
|
|
|
|
|
+ initialPageParam: 1,
|
|
|
enabled: isLoggedIn, // 只有登录后才获取数据
|
|
enabled: isLoggedIn, // 只有登录后才获取数据
|
|
|
- refetchOnWindowFocus: false
|
|
|
|
|
|
|
+ refetchOnWindowFocus: false,
|
|
|
|
|
+ staleTime: 30 * 1000, // 30秒缓存
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+ // 合并所有分页数据
|
|
|
|
|
+ const allTalents = data?.pages.flatMap(page => page.data) || []
|
|
|
|
|
+ const totalCount = data?.pages[0]?.total || 0
|
|
|
|
|
+
|
|
|
// 下拉刷新
|
|
// 下拉刷新
|
|
|
- const [refreshing, setRefreshing] = useState(false)
|
|
|
|
|
const onRefresh = async () => {
|
|
const onRefresh = async () => {
|
|
|
setRefreshing(true)
|
|
setRefreshing(true)
|
|
|
try {
|
|
try {
|
|
|
- await queryClient.invalidateQueries({ queryKey: ['talentList'] })
|
|
|
|
|
await refetch()
|
|
await refetch()
|
|
|
} finally {
|
|
} finally {
|
|
|
- setTimeout(() => setRefreshing(false), 1000)
|
|
|
|
|
|
|
+ setTimeout(() => setRefreshing(false), 500)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -126,6 +140,12 @@ const TalentManagement: React.FC<TalentManagementProps> = () => {
|
|
|
})
|
|
})
|
|
|
}, [])
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
+ // 页面显示时自动刷新(从详情页返回时触发)
|
|
|
|
|
+ useDidShow(() => {
|
|
|
|
|
+ console.debug('人才列表页显示,自动刷新数据')
|
|
|
|
|
+ refetch()
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
// 状态标签列表
|
|
// 状态标签列表
|
|
|
const statusTags: Array<'全部' | '在职' | '待入职' | '离职'> = ['全部', '在职', '待入职', '离职']
|
|
const statusTags: Array<'全部' | '在职' | '待入职' | '离职'> = ['全部', '在职', '待入职', '离职']
|
|
|
const disabilityTypeTags = ['肢体残疾', '听力残疾', '视力残疾', '言语残疾', '智力残疾', '精神残疾']
|
|
const disabilityTypeTags = ['肢体残疾', '听力残疾', '视力残疾', '言语残疾', '智力残疾', '精神残疾']
|
|
@@ -133,30 +153,22 @@ const TalentManagement: React.FC<TalentManagementProps> = () => {
|
|
|
// 处理状态筛选点击
|
|
// 处理状态筛选点击
|
|
|
const handleStatusClick = (status: '全部' | '在职' | '待入职' | '离职') => {
|
|
const handleStatusClick = (status: '全部' | '在职' | '待入职' | '离职') => {
|
|
|
setActiveStatus(status)
|
|
setActiveStatus(status)
|
|
|
- setPage(1)
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 处理残疾类型筛选点击
|
|
// 处理残疾类型筛选点击
|
|
|
const handleDisabilityTypeClick = (type: string) => {
|
|
const handleDisabilityTypeClick = (type: string) => {
|
|
|
setActiveDisabilityType(activeDisabilityType === type ? '' : type)
|
|
setActiveDisabilityType(activeDisabilityType === type ? '' : type)
|
|
|
- setPage(1)
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 处理搜索输入
|
|
// 处理搜索输入
|
|
|
- const handleSearchChange = (e: any) => {
|
|
|
|
|
|
|
+ const handleSearchChange = (e: { detail: { value: string } }) => {
|
|
|
setSearchText(e.detail.value)
|
|
setSearchText(e.detail.value)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 分页处理
|
|
|
|
|
- const handlePrevPage = () => {
|
|
|
|
|
- if (page > 1) {
|
|
|
|
|
- setPage(page - 1)
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- const handleNextPage = () => {
|
|
|
|
|
- if (talentList && page < Math.ceil(talentList.total / limit)) {
|
|
|
|
|
- setPage(page + 1)
|
|
|
|
|
|
|
+ // 触底加载更多
|
|
|
|
|
+ const handleScrollToLower = () => {
|
|
|
|
|
+ if (hasNextPage && !isFetchingNextPage) {
|
|
|
|
|
+ fetchNextPage()
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -186,7 +198,7 @@ const TalentManagement: React.FC<TalentManagementProps> = () => {
|
|
|
age--
|
|
age--
|
|
|
}
|
|
}
|
|
|
return `${age}岁`
|
|
return `${age}岁`
|
|
|
- } catch (error) {
|
|
|
|
|
|
|
+ } catch (_error) {
|
|
|
return '未知岁'
|
|
return '未知岁'
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -203,6 +215,7 @@ const TalentManagement: React.FC<TalentManagementProps> = () => {
|
|
|
<ScrollView
|
|
<ScrollView
|
|
|
className="h-[calc(100vh-120px)] overflow-y-auto px-4 pb-4 pt-0"
|
|
className="h-[calc(100vh-120px)] overflow-y-auto px-4 pb-4 pt-0"
|
|
|
scrollY
|
|
scrollY
|
|
|
|
|
+ onScrollToLower={handleScrollToLower}
|
|
|
refresherEnabled
|
|
refresherEnabled
|
|
|
refresherTriggered={refreshing}
|
|
refresherTriggered={refreshing}
|
|
|
onRefresherRefresh={onRefresh}
|
|
onRefresherRefresh={onRefresh}
|
|
@@ -266,7 +279,7 @@ const TalentManagement: React.FC<TalentManagementProps> = () => {
|
|
|
<View className="p-4">
|
|
<View className="p-4">
|
|
|
<View className="flex justify-between items-center mb-4">
|
|
<View className="flex justify-between items-center mb-4">
|
|
|
<Text className="font-semibold text-gray-700">
|
|
<Text className="font-semibold text-gray-700">
|
|
|
- 全部人才 ({talentList?.total || 0})
|
|
|
|
|
|
|
+ 全部人才 ({totalCount})
|
|
|
</Text>
|
|
</Text>
|
|
|
<View className="flex space-x-2">
|
|
<View className="flex space-x-2">
|
|
|
<View className="text-gray-500">
|
|
<View className="text-gray-500">
|
|
@@ -306,10 +319,10 @@ const TalentManagement: React.FC<TalentManagementProps> = () => {
|
|
|
重试
|
|
重试
|
|
|
</View>
|
|
</View>
|
|
|
</View>
|
|
</View>
|
|
|
- ) : talentList && talentList.data.length > 0 ? (
|
|
|
|
|
|
|
+ ) : allTalents.length > 0 ? (
|
|
|
// 人才列表
|
|
// 人才列表
|
|
|
<View className="space-y-3">
|
|
<View className="space-y-3">
|
|
|
- {talentList.data.map((talent) => (
|
|
|
|
|
|
|
+ {allTalents.map((talent) => (
|
|
|
<View
|
|
<View
|
|
|
key={talent.personId}
|
|
key={talent.personId}
|
|
|
className="card bg-white p-4 flex items-center cursor-pointer active:bg-gray-50"
|
|
className="card bg-white p-4 flex items-center cursor-pointer active:bg-gray-50"
|
|
@@ -346,28 +359,17 @@ const TalentManagement: React.FC<TalentManagementProps> = () => {
|
|
|
</View>
|
|
</View>
|
|
|
))}
|
|
))}
|
|
|
|
|
|
|
|
- {/* 分页控件 */}
|
|
|
|
|
- {talentList.total > limit && (
|
|
|
|
|
- <View className="flex justify-between items-center mt-4 pt-4 border-t border-gray-200">
|
|
|
|
|
- <View
|
|
|
|
|
- className={`text-xs px-3 py-1 rounded ${page === 1 ? 'text-gray-400' : 'text-blue-600'}`}
|
|
|
|
|
- onClick={handlePrevPage}
|
|
|
|
|
- >
|
|
|
|
|
- 上一页
|
|
|
|
|
- </View>
|
|
|
|
|
- <Text className="text-xs text-gray-600">
|
|
|
|
|
- 第 {page} 页 / 共 {Math.ceil(talentList.total / limit)} 页
|
|
|
|
|
- </Text>
|
|
|
|
|
- <View
|
|
|
|
|
- className={`text-xs px-3 py-1 rounded ${
|
|
|
|
|
- talentList && page >= Math.ceil(talentList.total / limit)
|
|
|
|
|
- ? 'text-gray-400'
|
|
|
|
|
- : 'text-blue-600'
|
|
|
|
|
- }`}
|
|
|
|
|
- onClick={handleNextPage}
|
|
|
|
|
- >
|
|
|
|
|
- 下一页
|
|
|
|
|
- </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 && allTalents.length > 0 && (
|
|
|
|
|
+ <View className="text-center py-4">
|
|
|
|
|
+ <Text className="text-sm text-gray-400">没有更多了</Text>
|
|
|
</View>
|
|
</View>
|
|
|
)}
|
|
)}
|
|
|
</View>
|
|
</View>
|