Hanye官网
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

JobCard.vue 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <template>
  2. <div
  3. class="job-card group"
  4. >
  5. <div class="relative p-8 bg-zinc-800/30 backdrop-blur-sm border border-zinc-700/30 rounded-2xl transition-all duration-700 hover:border-[#35F1FF]/50 hover:bg-zinc-800/50 hover:shadow-2xl hover:shadow-[#35F1FF]/10 hover:transform hover:scale-105">
  6. <div class="relative">
  7. <!-- 职位标题 -->
  8. <h4 class="text-white text-xl font-semibold mb-4 line-clamp-2 group-hover:text-[#35F1FF] transition-colors duration-500">
  9. {{ job.name }}
  10. </h4>
  11. <!-- 职位信息 -->
  12. <div class="flex flex-wrap items-center gap-6 text-zinc-400 text-sm mb-6">
  13. <span class="flex items-center">
  14. <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  15. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
  16. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
  17. </svg>
  18. {{ getLocationName(job.location) }}
  19. </span>
  20. <span class="flex items-center">
  21. <svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  22. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
  23. </svg>
  24. {{ formatDate(job.createTime) }}
  25. </span>
  26. </div>
  27. <!-- 工作职责 -->
  28. <div class="mb-6">
  29. <h5 class="text-white text-sm font-medium mb-3 flex items-center">
  30. <svg class="w-4 h-4 mr-2 text-[#35F1FF]" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  31. <rect x="4" y="2" width="16" height="20" rx="2" ry="2" />
  32. <line x1="8" y1="9" x2="16" y2="9" />
  33. <line x1="8" y1="13" x2="16" y2="13" />
  34. </svg>
  35. {{ t('careers.PositionCard.job') }}
  36. </h5>
  37. <div class="text-zinc-300/80 text-sm leading-relaxed line-clamp-3">
  38. {{ formatJobDescription(job.jobResponsibilities) }}
  39. </div>
  40. </div>
  41. <!-- 薪资福利 -->
  42. <div class="mb-6">
  43. <h5 class="text-white text-sm font-medium mb-3 flex items-center">
  44. <svg class="w-4 h-4 mr-2 text-[#35F1FF]" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  45. <path d="M12 2v20M17 5H9.5a3.5 3.5 0 100 7h5a3.5 3.5 0 110 7H7"></path>
  46. </svg>
  47. {{ t('careers.PositionCard.CB') }}
  48. </h5>
  49. <div class="text-zinc-300/80 text-sm leading-relaxed line-clamp-2">
  50. {{ formatBenefits(job.benefits) }}
  51. </div>
  52. </div>
  53. <!-- 工作时间 -->
  54. <div class="mb-6">
  55. <h5 class="text-white text-sm font-medium mb-3 flex items-center">
  56. <svg class="w-4 h-4 mr-2 text-[#35F1FF]" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  57. <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
  58. <line x1="16" y1="2" x2="16" y2="6"></line>
  59. <line x1="8" y1="2" x2="8" y2="6"></line>
  60. <line x1="3" y1="10" x2="21" y2="10"></line>
  61. </svg>
  62. {{ t('careers.PositionCard.workingHours') }}
  63. </h5>
  64. <div class="text-zinc-300/80 text-sm leading-relaxed">
  65. {{ formatWorkTime(job.workTime) }}
  66. </div>
  67. </div>
  68. <!-- 申请按钮 -->
  69. <div class="flex justify-between items-center pt-4 border-t border-zinc-700/30">
  70. <div class="text-xs text-zinc-500">
  71. {{ formatDate(job.updateTime) }} {{ t('careers.PositionCard.update') }}
  72. </div>
  73. <nuxt-link
  74. class="inline-flex items-center px-6 py-2 bg-[#35F1FF]/10 text-[#35F1FF] text-sm font-medium rounded-lg border border-[#35F1FF]/30 hover:bg-[#35F1FF]/20 hover:border-[#35F1FF]/50 transition-all duration-300 group-hover:scale-105"
  75. :to="`${homepagePath}/careers/${job.id}`"
  76. >
  77. <span class="mr-2">{{ t('careers.PositionCard.JobDetails') }}</span>
  78. <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  79. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
  80. </svg>
  81. </nuxt-link>
  82. </div>
  83. </div>
  84. </div>
  85. </div>
  86. </template>
  87. <script setup lang="ts">
  88. import type { Job } from '~/data/jobs'
  89. import { locationMap, workTypeMap } from '~/data/jobs'
  90. import { useI18n } from 'vue-i18n'
  91. const { t, locale } = useI18n()
  92. const homepagePath = computed(() => {
  93. return locale.value === "zh" ? "" : `/${locale.value}`;
  94. });
  95. /**
  96. * 组件属性
  97. */
  98. interface Props {
  99. job: Job
  100. }
  101. const props = defineProps<Props>()
  102. /**
  103. * 获取地点名称
  104. * @param location 地点编码
  105. * @returns 地点名称
  106. */
  107. const getLocationName = (location: number): string => {
  108. return locationMap[location] || 'Other'
  109. }
  110. /**
  111. * 格式化日期
  112. * @param dateString 日期字符串
  113. * @returns 格式化后的日期
  114. */
  115. const formatDate = (dateString: string | undefined): string => {
  116. if (!dateString) return ''
  117. try {
  118. const date = new Date(dateString)
  119. return date.toLocaleDateString('zh-CN', {
  120. year: 'numeric',
  121. month: '2-digit',
  122. day: '2-digit'
  123. })
  124. } catch {
  125. return dateString
  126. }
  127. }
  128. /**
  129. * 移除字符串中的HTML标签
  130. * @param htmlString 包含HTML的字符串
  131. * @returns 纯文本字符串
  132. */
  133. const stripHtml = (htmlString: string): string => {
  134. if (!htmlString) return ''
  135. return htmlString.replace(/<\/?[^>]+(>|$)/g, "")
  136. }
  137. /**
  138. * 格式化工作描述
  139. * @param description 工作描述
  140. * @returns 格式化后的描述
  141. */
  142. const formatJobDescription = (description: string): string => {
  143. if (!description) return ''
  144. const plainText = stripHtml(description)
  145. // 将数字开头的项目转换为更易读的格式
  146. return plainText
  147. .replace(/(\d+\.)/g, '\n$1')
  148. .replace(/;/g, ';\n')
  149. .trim()
  150. .substring(0, 200) + (plainText.length > 200 ? '...' : '')
  151. }
  152. /**
  153. * 格式化福利待遇
  154. * @param benefits 福利待遇
  155. * @returns 格式化后的福利
  156. */
  157. const formatBenefits = (benefits: string): string => {
  158. if (!benefits) return ''
  159. const plainText = stripHtml(benefits)
  160. return plainText
  161. .replace(/(\d+\.)/g, '\n$1')
  162. .trim()
  163. .substring(0, 100) + (plainText.length > 100 ? '...' : '')
  164. }
  165. /**
  166. * 格式化工作时间
  167. * @param workTime 工作时间
  168. * @returns 格式化后的工作时间
  169. */
  170. const formatWorkTime = (workTime: string): string => {
  171. if (!workTime) return ''
  172. const plainText = stripHtml(workTime)
  173. return plainText
  174. .replace(/\\n/g, '\n')
  175. .replace(/(\d+\.)/g, '\n$1')
  176. .trim()
  177. }
  178. </script>
  179. <style scoped>
  180. .job-card {
  181. opacity: 0;
  182. transform: translateY(20px);
  183. transition: opacity 0.8s ease, transform 0.8s ease;
  184. }
  185. .job-card.animate-in {
  186. opacity: 1;
  187. transform: translateY(0);
  188. }
  189. </style>