|
|
@@ -1,14 +1,246 @@
|
|
|
-import React from 'react'
|
|
|
-import { View, Text } from '@tarojs/components'
|
|
|
+import React, { useEffect, useState } from 'react'
|
|
|
+import { View, Text, ScrollView } from '@tarojs/components'
|
|
|
+import Taro from '@tarojs/taro'
|
|
|
+import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
|
import YongrenTabBarLayout from '@/layouts/yongren-tab-bar-layout'
|
|
|
+import { enterpriseCompanyClient } from '@/api'
|
|
|
+import { useAuth } from '@/utils/auth'
|
|
|
+import { useRequireAuth } from '@/hooks/useRequireAuth'
|
|
|
+import './index.css'
|
|
|
+
|
|
|
+// 类型定义
|
|
|
+interface OverviewData {
|
|
|
+ totalEmployees: number
|
|
|
+ pendingAssignments: number
|
|
|
+ monthlyOrders: number
|
|
|
+ companyName: string
|
|
|
+}
|
|
|
+
|
|
|
+interface AllocationData {
|
|
|
+ id: string
|
|
|
+ name: string
|
|
|
+ avatarColor: 'blue' | 'green' | 'purple' | 'orange'
|
|
|
+ disabilityType: string
|
|
|
+ disabilityLevel: string
|
|
|
+ status: '在职' | '待入职' | '离职'
|
|
|
+ joinDate: string
|
|
|
+ salary: number
|
|
|
+ progress: number
|
|
|
+}
|
|
|
|
|
|
const YongrenDashboardPage: React.FC = () => {
|
|
|
+ const { user } = useAuth()
|
|
|
+ const [refreshing, setRefreshing] = useState(false)
|
|
|
+ const queryClient = useQueryClient()
|
|
|
+
|
|
|
+ // 检查登录状态,未登录则重定向
|
|
|
+ useRequireAuth()
|
|
|
+
|
|
|
+ // 获取企业概览数据
|
|
|
+ const { data: overview, isLoading: overviewLoading } = useQuery({
|
|
|
+ queryKey: ['enterpriseOverview'],
|
|
|
+ queryFn: async () => {
|
|
|
+ const response = await enterpriseCompanyClient.overview.get()
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('获取企业概览数据失败')
|
|
|
+ }
|
|
|
+ return await response.json() as OverviewData
|
|
|
+ },
|
|
|
+ refetchOnWindowFocus: false
|
|
|
+ })
|
|
|
+
|
|
|
+ // 获取近期分配人才列表
|
|
|
+ const { data: allocations, isLoading: allocationsLoading } = useQuery({
|
|
|
+ queryKey: ['recentAllocations'],
|
|
|
+ queryFn: async () => {
|
|
|
+ const response = await enterpriseCompanyClient.allocations.recent.get()
|
|
|
+ if (response.status !== 200) {
|
|
|
+ throw new Error('获取分配人才列表失败')
|
|
|
+ }
|
|
|
+ return await response.json() as AllocationData[]
|
|
|
+ },
|
|
|
+ refetchOnWindowFocus: false
|
|
|
+ })
|
|
|
+
|
|
|
+ // 下拉刷新
|
|
|
+ const onRefresh = async () => {
|
|
|
+ setRefreshing(true)
|
|
|
+ try {
|
|
|
+ await Promise.all([
|
|
|
+ queryClient.invalidateQueries({ queryKey: ['enterpriseOverview'] }),
|
|
|
+ queryClient.invalidateQueries({ queryKey: ['recentAllocations'] })
|
|
|
+ ])
|
|
|
+ } finally {
|
|
|
+ setTimeout(() => setRefreshing(false), 1000)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 页面加载时设置标题
|
|
|
+ useEffect(() => {
|
|
|
+ Taro.setNavigationBarTitle({
|
|
|
+ title: '企业仪表板'
|
|
|
+ })
|
|
|
+ }, [])
|
|
|
+
|
|
|
+ const isLoading = overviewLoading || allocationsLoading
|
|
|
+
|
|
|
return (
|
|
|
<YongrenTabBarLayout activeKey="dashboard">
|
|
|
- <View className="p-4">
|
|
|
- <Text className="text-xl font-bold">企业仪表板</Text>
|
|
|
- <Text className="text-gray-600 mt-2">企业概览和数据统计(待实现)</Text>
|
|
|
- </View>
|
|
|
+ <ScrollView
|
|
|
+ className="h-[calc(100%-60px)] overflow-y-auto p-4"
|
|
|
+ scrollY
|
|
|
+ refresherEnabled
|
|
|
+ refresherTriggered={refreshing}
|
|
|
+ onRefresherRefresh={onRefresh}
|
|
|
+ >
|
|
|
+ {/* 顶部信息栏 - 对照原型第276-300行 */}
|
|
|
+ <View className="bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-2xl p-5 mb-4">
|
|
|
+ <View className="flex justify-between items-center">
|
|
|
+ <View>
|
|
|
+ <Text className="text-sm opacity-80">欢迎回来</Text>
|
|
|
+ <Text className="text-xl font-bold">
|
|
|
+ {overview?.companyName || user?.companyName || '企业名称'}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ <View className="w-12 h-12 rounded-full bg-white/20 flex items-center justify-center">
|
|
|
+ <View className="i-heroicons-building-office-20-solid text-white text-xl" />
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ <View className="mt-4 flex justify-between">
|
|
|
+ <View className="text-center">
|
|
|
+ <Text className="text-2xl font-bold">{overview?.totalEmployees || 0}</Text>
|
|
|
+ <Text className="text-xs opacity-80">在职人员</Text>
|
|
|
+ </View>
|
|
|
+ <View className="text-center">
|
|
|
+ <Text className="text-2xl font-bold">{overview?.pendingAssignments || 0}</Text>
|
|
|
+ <Text className="text-xs opacity-80">待入职</Text>
|
|
|
+ </View>
|
|
|
+ <View className="text-center">
|
|
|
+ <Text className="text-2xl font-bold">{overview?.monthlyOrders || 0}</Text>
|
|
|
+ <Text className="text-xs opacity-80">本月新增</Text>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 快速操作网格 - 对照原型第303-320行 */}
|
|
|
+ <View className="grid grid-cols-4 gap-3 mb-4">
|
|
|
+ <View className="bg-blue-50 rounded-xl p-3 text-center">
|
|
|
+ <View className="i-heroicons-user-group-20-solid text-blue-500 text-lg mb-1" />
|
|
|
+ <Text className="text-xs text-gray-700">人才库</Text>
|
|
|
+ </View>
|
|
|
+ <View className="bg-green-50 rounded-xl p-3 text-center">
|
|
|
+ <View className="i-heroicons-chart-bar-20-solid text-green-500 text-lg mb-1" />
|
|
|
+ <Text className="text-xs text-gray-700">数据统计</Text>
|
|
|
+ </View>
|
|
|
+ <View className="bg-purple-50 rounded-xl p-3 text-center">
|
|
|
+ <View className="i-heroicons-document-text-20-solid text-purple-500 text-lg mb-1" />
|
|
|
+ <Text className="text-xs text-gray-700">订单管理</Text>
|
|
|
+ </View>
|
|
|
+ <View className="bg-yellow-50 rounded-xl p-3 text-center">
|
|
|
+ <View className="i-heroicons-cog-6-tooth-20-solid text-yellow-500 text-lg mb-1" />
|
|
|
+ <Text className="text-xs text-gray-700">设置</Text>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 人才列表区域 - 对照原型第323-376行 */}
|
|
|
+ <View className="mb-4">
|
|
|
+ <View className="flex justify-between items-center mb-3">
|
|
|
+ <Text className="font-semibold text-gray-700">分配人才</Text>
|
|
|
+ <Text className="text-xs text-blue-500">查看全部</Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {allocationsLoading ? (
|
|
|
+ <View className="space-y-3">
|
|
|
+ {[1, 2].map((i) => (
|
|
|
+ <View key={i} className="bg-white p-4 rounded-lg animate-pulse">
|
|
|
+ <View className="flex items-center">
|
|
|
+ <View className="w-10 h-10 bg-gray-200 rounded-full" />
|
|
|
+ <View className="flex-1 ml-3">
|
|
|
+ <View className="h-4 bg-gray-200 rounded w-1/3 mb-2" />
|
|
|
+ <View className="h-3 bg-gray-200 rounded w-1/2" />
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ ))}
|
|
|
+ </View>
|
|
|
+ ) : allocations && allocations.length > 0 ? (
|
|
|
+ <View className="space-y-3">
|
|
|
+ {allocations.slice(0, 2).map((allocation) => (
|
|
|
+ <View key={allocation.id} className="bg-white p-4 rounded-lg flex items-center">
|
|
|
+ {/* 头像区域 */}
|
|
|
+ <View className={`name-avatar ${allocation.avatarColor} w-10 h-10 rounded-full flex items-center justify-center`}>
|
|
|
+ <Text className="text-white font-semibold">
|
|
|
+ {allocation.name.charAt(0)}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 信息区域 */}
|
|
|
+ <View className="flex-1 ml-3">
|
|
|
+ <View className="flex justify-between items-start">
|
|
|
+ <View>
|
|
|
+ <Text className="font-semibold text-gray-800">{allocation.name}</Text>
|
|
|
+ <Text className="text-xs text-gray-500">
|
|
|
+ {allocation.disabilityType} · {allocation.disabilityLevel}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ <Text className={`text-xs px-2 py-1 rounded-full ${
|
|
|
+ allocation.status === '在职'
|
|
|
+ ? 'bg-green-100 text-green-800'
|
|
|
+ : allocation.status === '待入职'
|
|
|
+ ? 'bg-yellow-100 text-yellow-800'
|
|
|
+ : 'bg-gray-100 text-gray-800'
|
|
|
+ }`}>
|
|
|
+ {allocation.status}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+
|
|
|
+ <View className="mt-2">
|
|
|
+ <View className="flex justify-between text-xs text-gray-500 mb-1">
|
|
|
+ <Text>{allocation.status === '在职' ? '入职时间:' : '预计入职:'} {allocation.joinDate}</Text>
|
|
|
+ <Text>薪资: ¥{allocation.salary.toLocaleString()}</Text>
|
|
|
+ </View>
|
|
|
+ <View className="progress-bar">
|
|
|
+ <View
|
|
|
+ className="progress-fill"
|
|
|
+ style={{ width: `${allocation.progress}%` }}
|
|
|
+ />
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ ))}
|
|
|
+ </View>
|
|
|
+ ) : (
|
|
|
+ <View className="bg-white p-4 rounded-lg text-center">
|
|
|
+ <Text className="text-gray-500 text-sm">暂无分配人才</Text>
|
|
|
+ </View>
|
|
|
+ )}
|
|
|
+ </View>
|
|
|
+
|
|
|
+ {/* 数据统计卡片 - 对照原型第379-394行 */}
|
|
|
+ <View className="mb-4">
|
|
|
+ <Text className="font-semibold text-gray-700 mb-3">数据统计</Text>
|
|
|
+ <View className="grid grid-cols-2 gap-3">
|
|
|
+ <View className="stat-card bg-white p-4 rounded-lg">
|
|
|
+ <View className="flex items-center mb-2">
|
|
|
+ <View className="pulse-dot mr-2" />
|
|
|
+ <Text className="text-sm text-gray-600">在职率</Text>
|
|
|
+ </View>
|
|
|
+ <Text className="text-2xl font-bold text-gray-800">
|
|
|
+ {overview?.totalEmployees ? '92%' : '--'}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ <View className="stat-card bg-white p-4 rounded-lg">
|
|
|
+ <Text className="text-sm text-gray-600 mb-2">平均薪资</Text>
|
|
|
+ <Text className="text-2xl font-bold text-gray-800">
|
|
|
+ {allocations && allocations.length > 0
|
|
|
+ ? `¥${Math.round(allocations.reduce((sum, a) => sum + a.salary, 0) / allocations.length).toLocaleString()}`
|
|
|
+ : '¥0'}
|
|
|
+ </Text>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </View>
|
|
|
+ </ScrollView>
|
|
|
</YongrenTabBarLayout>
|
|
|
)
|
|
|
}
|