Przeglądaj źródła

✨ feat(homepage): 优化银龄岗位展示卡片

- 添加薪资格式化工具函数formatSalary,支持面议、固定薪资和薪资范围显示
- 添加发布时间格式化工具函数formatPublishDate,显示今天/昨天/X天前/X周前/X月前
- 重构transformSilverPositions函数,解析薪资范围为min/max,提取福利标签数组
- 改进岗位卡片UI设计,增加企业logo、职位类型标签、工作地点和薪资信息
- 添加职位描述预览和福利标签展示,最多显示3个福利标签
- 底部新增浏览量、投递人数和发布时间信息,提升信息透明度
- 优化排序逻辑,使用publishDate替代createdAt进行排序
yourname 7 miesięcy temu
rodzic
commit
e97c4ec2e6
1 zmienionych plików z 158 dodań i 70 usunięć
  1. 158 70
      src/client/mobile/pages/NewHomePage.tsx

+ 158 - 70
src/client/mobile/pages/NewHomePage.tsx

@@ -23,6 +23,26 @@ import {
   TrophyIcon
 } from '@heroicons/react/24/outline';
 
+// 工具函数:格式化薪资显示
+const formatSalary = (min: number, max: number): string => {
+  if (min === 0 && max === 0) return '面议';
+  if (min === max) return `¥${min.toLocaleString()}/月`;
+  return `¥${min.toLocaleString()}-${max.toLocaleString()}/月`;
+};
+
+// 工具函数:格式化发布时间
+const formatPublishDate = (date: string): string => {
+  const publishDate = new Date(date);
+  const now = new Date();
+  const diffDays = Math.floor((now.getTime() - publishDate.getTime()) / (1000 * 60 * 60 * 24));
+  
+  if (diffDays === 0) return '今天';
+  if (diffDays === 1) return '昨天';
+  if (diffDays < 7) return `${diffDays}天前`;
+  if (diffDays < 30) return `${Math.floor(diffDays / 7)}周前`;
+  return `${Math.floor(diffDays / 30)}月前`;
+};
+
 // 中国水墨风格色彩方案
 const COLORS = {
   ink: {
@@ -149,6 +169,7 @@ const transformSilverTalents = (talents: any[]) =>
     jobSeekingStatus: talent.jobSeekingStatus === 1 ? 'ACTIVELY_SEEKING' : talent.jobSeekingStatus === 2 ? 'OPEN_TO_OPPORTUNITIES' : 'NOT_SEEKING',
     knowledgeShareCount: talent.knowledgeShareCount || 0,
     knowledgeReadCount: talent.knowledgeReadCount || 0,
+    knowledgeLikeCount: talent.knowledgeLikeCount || 0,
     knowledgeRankingScore: parseFloat(talent.knowledgeRankingScore || 0),
     totalPoints: talent.totalPoints || 0,
     city: talent.city || '未知地区',
@@ -156,16 +177,45 @@ const transformSilverTalents = (talents: any[]) =>
   }));
 
 const transformSilverPositions = (positions: any[]) =>
-  positions.map(position => ({
-    id: position.id,
-    title: position.title,
-    organization: position.company?.name || '未知单位',
-    budget: position.salaryRange || '面议',
-    deadline: position.deadline || new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
-    location: position.location || '全国',
-    category: position.category?.name || '其他',
-    createdAt: position.createdAt
-  }));
+  positions.map(position => {
+    // 解析薪资范围
+    let salaryMin = 0;
+    let salaryMax = 0;
+    if (position.salaryRange) {
+      const salaryMatch = position.salaryRange.match(/(\d+)-(\d+)/);
+      if (salaryMatch) {
+        salaryMin = parseInt(salaryMatch[1]);
+        salaryMax = parseInt(salaryMatch[2]);
+      } else {
+        const singleSalary = parseInt(position.salaryRange.replace(/\D/g, ''));
+        salaryMin = singleSalary;
+        salaryMax = singleSalary;
+      }
+    }
+
+    // 转换福利标签
+    let benefits: string[] = [];
+    if (position.benefits) {
+      benefits = position.benefits.split(',').map((benefit: string) => benefit.trim()).filter((benefit: string) => benefit);
+    }
+
+    return {
+      id: position.id,
+      title: position.title,
+      companyName: position.company?.name || '未知企业',
+      location: position.location || '全国',
+      salaryMin,
+      salaryMax,
+      jobType: position.jobType || '全职',
+      description: position.description || '',
+      requirements: position.requirements || '',
+      benefits,
+      publishDate: position.createdAt,
+      views: position.viewCount || 0,
+      applications: position.applicationCount || 0,
+      companyLogo: position.company?.logo
+    };
+  });
 
 // 主页面组件
 const NewHomePage: React.FC = () => {
@@ -230,7 +280,7 @@ const NewHomePage: React.FC = () => {
   
   // 使用真实的银龄岗位数据
   const silverPositions = transformSilverPositions(homeData.silverPositions || [])
-    .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
+    .sort((a, b) => new Date(b.publishDate).getTime() - new Date(a.publishDate).getTime())
     .slice(0, 3);
   
   // 使用真实的银龄人才数据
@@ -822,78 +872,116 @@ const NewHomePage: React.FC = () => {
             </div>
             
             <div className="space-y-3">
-              {silverPositions.map((position) => (
+              {silverPositions.map((job) => (
                 <div
-                  key={position.id}
-                  className="flex items-center justify-between p-4 rounded-xl shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer backdrop-blur-sm"
+                  key={job.id}
+                  className="mb-3 mx-0 rounded-xl shadow-sm hover:shadow-lg transition-all duration-300 cursor-pointer backdrop-blur-sm border overflow-hidden"
                   style={{
                     backgroundColor: 'rgba(255,255,255,0.8)',
-                    border: `1px solid ${COLORS.ink.medium}`
+                    borderColor: COLORS.ink.medium
                   }}
-                  onClick={() => navigate(`/silver-jobs/${position.id}`)}
+                  onClick={() => navigate(`/silver-jobs/${job.id}`)}
                 >
-                  <div className="flex items-center flex-1">
-                    <img
-                      src={position.image || unsplash.getJobImage(position.id)}
-                      alt={position.title}
-                      className="w-14 h-14 rounded-full object-cover mr-4 border-2 shadow-sm"
-                      style={{ borderColor: COLORS.ink.medium }}
-                    />
-                    <div className="flex-1">
-                      <h4
-                        className={`font-medium ${FONT_STYLES.body}`}
-                        style={{ color: COLORS.text.primary }}
-                      >
-                        {position.title}
-                      </h4>
-                      <p
-                        className={`${FONT_STYLES.caption}`}
-                        style={{ color: COLORS.text.secondary }}
-                      >
-                        {position.organization}
-                      </p>
-                      <div className="flex items-center mt-1 space-x-2">
-                        <span
-                          className={`${FONT_STYLES.small} px-2 py-1 rounded-full flex items-center`}
-                          style={{
-                            backgroundColor: COLORS.accent.green,
-                            color: 'white'
-                          }}
+                  <div className="p-4">
+                    {/* 顶部信息 */}
+                    <div className="flex items-start justify-between mb-3">
+                      <div className="flex items-center">
+                        <div
+                          className="w-14 h-14 rounded-full mr-3 flex items-center justify-center text-lg font-bold border-2"
+                          style={{ borderColor: COLORS.ink.medium }}
                         >
-                          {position.budget}
-                        </span>
+                          {job.companyLogo ? (
+                            <img
+                              src={job.companyLogo}
+                              alt={job.companyName}
+                              className="w-full h-full rounded-full object-cover"
+                            />
+                          ) : (
+                            <span style={{ color: COLORS.ink.dark }}>
+                              {job.companyName.slice(0, 2)}
+                            </span>
+                          )}
+                        </div>
+                        <div>
+                          <h3
+                            className="font-serif text-lg font-medium leading-snug"
+                            style={{ color: COLORS.text.primary }}
+                          >
+                            {job.title}
+                          </h3>
+                          <p
+                            className="font-sans text-sm leading-normal mt-1"
+                            style={{ color: COLORS.text.secondary }}
+                          >
+                            {job.companyName}
+                          </p>
+                        </div>
+                      </div>
+                      <span
+                        className="px-2 py-1 rounded-full text-xs font-medium"
+                        style={{ backgroundColor: COLORS.accent.blue, color: 'white' }}
+                      >
+                        {job.jobType}
+                      </span>
+                    </div>
+
+                    {/* 基本信息 */}
+                    <div className="flex items-center justify-between mb-2">
+                      <div className="flex items-center">
+                        <span className="text-sm mr-2">📍</span>
                         <span
-                          className={`${FONT_STYLES.small} px-2 py-1 rounded-full flex items-center`}
-                          style={{
-                            backgroundColor: COLORS.accent.blue,
-                            color: 'white'
-                          }}
+                          className="font-sans text-sm"
+                          style={{ color: COLORS.text.secondary }}
                         >
-                          <MapPinIcon className="w-3 h-3 mr-1" />
-                          {position.location}
+                          {job.location}
                         </span>
                       </div>
+                      <span className="text-sm">💰 {formatSalary(job.salaryMin, job.salaryMax)}</span>
                     </div>
-                  </div>
-                  <div className="text-right flex flex-col items-end">
-                    <span
-                      className={`text-xs px-2 py-1 rounded-full mb-1`}
-                      style={{
-                        color: COLORS.text.light,
-                        backgroundColor: COLORS.ink.light
-                      }}
+
+                    {/* 职位描述 */}
+                    <p
+                      className="font-sans text-sm leading-relaxed mb-3 line-clamp-2"
+                      style={{ color: COLORS.text.light }}
                     >
-                      截止: {new Date(position.deadline).toLocaleDateString()}
-                    </span>
-                    <span
-                      className={`text-xs px-2 py-1 rounded-full`}
-                      style={{
-                        backgroundColor: COLORS.accent.green,
-                        color: 'white'
-                      }}
+                      {job.description}
+                    </p>
+
+                    {/* 福利标签 */}
+                    {job.benefits.length > 0 && (
+                      <div className="flex flex-wrap gap-1 mb-3">
+                        {job.benefits.slice(0, 3).map((benefit: string, index: number) => (
+                          <span
+                            key={index}
+                            className="px-2 py-1 text-xs rounded-full"
+                            style={{
+                              backgroundColor: COLORS.accent.green + '20',
+                              color: COLORS.accent.green,
+                              borderColor: COLORS.accent.green + '30'
+                            }}
+                          >
+                            {benefit}
+                          </span>
+                        ))}
+                      </div>
+                    )}
+
+                    {/* 底部信息 */}
+                    <div className="flex items-center justify-between text-xs pt-3 border-t"
+                         style={{ borderColor: COLORS.ink.medium }}
                     >
-                      {position.category}
-                    </span>
+                      <div className="flex items-center space-x-3">
+                        <span style={{ color: COLORS.text.light }}>
+                          👁️ {job.views}
+                        </span>
+                        <span style={{ color: COLORS.text.light }}>
+                          📋 {job.applications}人投递
+                        </span>
+                      </div>
+                      <span style={{ color: COLORS.text.light }}>
+                        {formatPublishDate(job.publishDate)}
+                      </span>
+                    </div>
                   </div>
                 </div>
               ))}