JobDetailModal.vue 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. <script setup lang="ts">
  2. import { computed } from 'vue'
  3. import { useJobStore, type Job } from '../stores/jobStore'
  4. import { X, Briefcase, MapPin, Users, Calendar, Star } from 'lucide-vue-next'
  5. interface Props {
  6. isOpen: boolean
  7. job: Job | null
  8. }
  9. interface Emits {
  10. (e: 'close'): void
  11. }
  12. const props = defineProps<Props>()
  13. const emit = defineEmits<Emits>()
  14. const jobStore = useJobStore()
  15. const handleClose = () => {
  16. emit('close')
  17. }
  18. const getStatusConfig = (status: Job['status']) => {
  19. switch (status) {
  20. case 'active':
  21. return { label: '招聘中', class: 'bg-success-100 text-success-700' }
  22. case 'paused':
  23. return { label: '已暂停', class: 'bg-gray-100 text-gray-700' }
  24. case 'draft':
  25. return { label: '草稿', class: 'bg-warning-100 text-warning-700' }
  26. default:
  27. return { label: '未知', class: 'bg-gray-100 text-gray-500' }
  28. }
  29. }
  30. const statusConfig = computed(() =>
  31. props.job ? getStatusConfig(props.job.status) : { label: '', class: '' }
  32. )
  33. </script>
  34. <template>
  35. <Teleport to="body">
  36. <Transition name="modal" appear>
  37. <div
  38. v-if="isOpen && job"
  39. class="fixed inset-0 z-50 flex items-end sm:items-center justify-center p-4"
  40. @click.self="handleClose"
  41. >
  42. <!-- 背景遮罩 -->
  43. <div class="absolute inset-0 bg-black/50 backdrop-blur-sm"></div>
  44. <!-- 模态框内容 -->
  45. <div
  46. class="relative w-full max-w-2xl max-h-[90vh] bg-white rounded-t-3xl sm:rounded-3xl shadow-2xl overflow-hidden"
  47. v-motion-slide-bottom
  48. >
  49. <!-- 模态框头部 -->
  50. <div class="flex items-center justify-between p-6 border-b border-gray-100">
  51. <div class="flex items-center gap-3">
  52. <h2 class="text-2xl font-bold text-gray-900">岗位详情</h2>
  53. <span :class="['px-3 py-1 rounded-full text-xs font-medium', statusConfig.class]">
  54. {{ statusConfig.label }}
  55. </span>
  56. </div>
  57. <button
  58. @click="handleClose"
  59. class="p-2 rounded-full hover:bg-gray-100 transition-colors"
  60. >
  61. <X :size="20" class="text-gray-500" />
  62. </button>
  63. </div>
  64. <!-- 模态框内容 -->
  65. <div class="p-6 overflow-y-auto max-h-[calc(90vh-140px)]">
  66. <div class="space-y-8">
  67. <!-- 基础信息 -->
  68. <section>
  69. <h3 class="text-lg font-semibold text-gray-900 mb-4">基础信息</h3>
  70. <div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
  71. <div class="bg-gray-50 rounded-xl p-4">
  72. <div class="flex items-center gap-2 mb-2">
  73. <Briefcase :size="16" class="text-primary-600" />
  74. <span class="text-sm font-medium text-gray-700">岗位名称</span>
  75. </div>
  76. <p class="text-gray-900 font-semibold">{{ job.title }}</p>
  77. </div>
  78. <div class="bg-gray-50 rounded-xl p-4">
  79. <div class="flex items-center gap-2 mb-2">
  80. <Users :size="16" class="text-primary-600" />
  81. <span class="text-sm font-medium text-gray-700">所属部门</span>
  82. </div>
  83. <p class="text-gray-900 font-semibold">{{ job.department }}</p>
  84. </div>
  85. <div class="bg-gray-50 rounded-xl p-4">
  86. <div class="flex items-center gap-2 mb-2">
  87. <MapPin :size="16" class="text-primary-600" />
  88. <span class="text-sm font-medium text-gray-700">工作地点</span>
  89. </div>
  90. <p class="text-gray-900 font-semibold">{{ job.location }}</p>
  91. </div>
  92. <div class="bg-gray-50 rounded-xl p-4">
  93. <div class="flex items-center gap-2 mb-2">
  94. <Calendar :size="16" class="text-primary-600" />
  95. <span class="text-sm font-medium text-gray-700">创建时间</span>
  96. </div>
  97. <p class="text-gray-900 font-semibold">{{ new Date(job.createdAt).toLocaleDateString() }}</p>
  98. </div>
  99. </div>
  100. </section>
  101. <!-- 岗位描述 -->
  102. <section>
  103. <h3 class="text-lg font-semibold text-gray-900 mb-4">岗位描述 (JD)</h3>
  104. <div class="bg-gray-50 rounded-xl p-6">
  105. <pre class="whitespace-pre-wrap text-gray-700 leading-relaxed font-sans">{{ job.description }}</pre>
  106. </div>
  107. </section>
  108. <!-- 招聘统计 -->
  109. <section>
  110. <h3 class="text-lg font-semibold text-gray-900 mb-4">招聘统计</h3>
  111. <div class="grid grid-cols-2 gap-4">
  112. <div class="text-center p-4 bg-gradient-to-br from-warning-50 to-orange-50 rounded-xl border border-warning-100">
  113. <div class="text-2xl font-bold text-warning-600 mb-1">
  114. {{ job.pendingResumes }}
  115. </div>
  116. <div class="text-xs text-warning-700 font-medium">待处理简历</div>
  117. </div>
  118. <div class="text-center p-4 bg-gradient-to-br from-success-50 to-green-50 rounded-xl border border-success-100">
  119. <div class="text-2xl font-bold text-success-600 mb-1">
  120. {{ job.passedResumes }}
  121. </div>
  122. <div class="text-xs text-success-700 font-medium">已通过初筛</div>
  123. </div>
  124. </div>
  125. </section>
  126. <!-- AI筛选评分标准 -->
  127. <section>
  128. <h3 class="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
  129. <Star :size="20" class="text-primary-600" />
  130. AI筛选评分标准
  131. </h3>
  132. <div class="space-y-4">
  133. <!-- 学历要求 -->
  134. <div class="p-4 border border-gray-200 rounded-xl">
  135. <div class="flex items-center justify-between mb-2">
  136. <h4 class="font-medium text-gray-900">学历要求</h4>
  137. <span class="text-sm font-medium text-primary-600">
  138. {{ job.aiCriteria.education.weight }}%
  139. </span>
  140. </div>
  141. <p class="text-gray-700">
  142. {{ job.aiCriteria.education.condition === '>=' ? '大于等于' :
  143. job.aiCriteria.education.condition === '=' ? '等于' : '包含' }}
  144. {{ job.aiCriteria.education.value }}
  145. </p>
  146. </div>
  147. <!-- 工作经验 -->
  148. <div class="p-4 border border-gray-200 rounded-xl">
  149. <div class="flex items-center justify-between mb-2">
  150. <h4 class="font-medium text-gray-900">工作经验</h4>
  151. <span class="text-sm font-medium text-primary-600">
  152. {{ job.aiCriteria.experience.weight }}%
  153. </span>
  154. </div>
  155. <p class="text-gray-700">
  156. {{ job.aiCriteria.experience.condition === '>=' ? '大于等于' :
  157. job.aiCriteria.experience.condition === '=' ? '等于' : '包含' }}
  158. {{ job.aiCriteria.experience.value }}
  159. </p>
  160. </div>
  161. <!-- 技术能力 -->
  162. <div class="p-4 border border-gray-200 rounded-xl">
  163. <div class="flex items-center justify-between mb-2">
  164. <h4 class="font-medium text-gray-900">技术能力</h4>
  165. <span class="text-sm font-medium text-primary-600">
  166. {{ job.aiCriteria.skills.weight }}%
  167. </span>
  168. </div>
  169. <p class="text-gray-700">
  170. {{ job.aiCriteria.skills.condition === '>=' ? '大于等于' :
  171. job.aiCriteria.skills.condition === '=' ? '等于' : '包含' }}
  172. {{ job.aiCriteria.skills.value || '无特殊要求' }}
  173. </p>
  174. </div>
  175. <!-- 语言要求 -->
  176. <div class="p-4 border border-gray-200 rounded-xl">
  177. <div class="flex items-center justify-between mb-2">
  178. <h4 class="font-medium text-gray-900">语言要求</h4>
  179. <span class="text-sm font-medium text-primary-600">
  180. {{ job.aiCriteria.language.weight }}%
  181. </span>
  182. </div>
  183. <p class="text-gray-700">
  184. {{ job.aiCriteria.language.condition === '>=' ? '大于等于' :
  185. job.aiCriteria.language.condition === '=' ? '等于' : '包含' }}
  186. {{ job.aiCriteria.language.value }}
  187. </p>
  188. </div>
  189. </div>
  190. </section>
  191. </div>
  192. </div>
  193. </div>
  194. </div>
  195. </Transition>
  196. </Teleport>
  197. </template>
  198. <style scoped>
  199. /* 模态框动画 */
  200. .modal-enter-active,
  201. .modal-leave-active {
  202. transition: all 0.3s ease-out;
  203. }
  204. .modal-enter-from,
  205. .modal-leave-to {
  206. opacity: 0;
  207. }
  208. .modal-enter-from .relative,
  209. .modal-leave-to .relative {
  210. transform: translateY(100px) scale(0.95);
  211. }
  212. </style>