Files
offerpai_web/src/components/AgentJobPreviewPanel.vue
T
2026-07-10 02:08:41 +08:00

212 lines
8.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<!-- Agent岗位预览抽屉 从浏览器右侧弹出 -->
<Teleport to="body">
<Transition name="agent-job-drawer">
<div v-if="visible" class="agent-job-drawer-mask" @click.self="handleBack">
<div class="agent-job-drawer" v-loading="loading" element-loading-text="加载中...">
<!-- 顶部标题栏 -->
<div class="agent-job-drawer__header border-F0F0F0-bottom-1">
<div class="agent-job-drawer__header-left">
<!-- 关闭按钮 -->
<div class="agent-job-drawer__close" @click="handleBack">
<svg viewBox="0 0 24 24" fill="none" class="agent-job-drawer__close-icon">
<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<span class="agent-job-drawer__title">岗位详情</span>
</div>
</div>
<!-- 岗位详情内容 -->
<div v-if="jobDetail" class="agent-job-drawer__body">
<!-- 岗位标题区域 -->
<div class="agent-job-drawer__job-header">
<!-- 左侧信息 -->
<div class="agent-job-drawer__job-info">
<h2 class="agent-job-drawer__job-title">{{ jobDetail.jobTitle }}</h2>
<!-- 岗位元信息 -->
<div class="agent-job-drawer__meta">
<span v-if="jobDetail.regionName">{{ jobDetail.regionName }}</span>
<span v-if="jobDetail.regionName && formatExperience"> · </span>
<span v-if="formatExperience">{{ formatExperience }}</span>
<span v-if="formatExperience && formatRecruitCategory"> · </span>
<span v-if="formatRecruitCategory">{{ formatRecruitCategory }}</span>
<span v-if="formatRecruitCategory && formatEducation"> · </span>
<span v-if="formatEducation">{{ formatEducation }}</span>
<span v-if="formatEducation && jobDetail.salary"> · </span>
<span v-if="jobDetail.salary">{{ jobDetail.salary }}</span>
</div>
</div>
<!-- 右侧匹配度环 -->
<div class="agent-job-drawer__match">
<svg viewBox="0 0 62 62" class="agent-job-drawer__ring-svg">
<circle cx="31" cy="31" r="27" stroke-width="4" stroke="#E8E8E8" fill="none" />
<circle cx="31" cy="31" r="27" stroke-width="4" fill="none"
stroke="#9FD051" stroke-linecap="round"
:stroke-dasharray="2 * Math.PI * 27"
:stroke-dashoffset="2 * Math.PI * 27 * (1 - (jobDetail.matchScore || 0) / 100)"
transform="rotate(-90 31 31)"
/>
</svg>
<span class="agent-job-drawer__match-text">{{ jobDetail.matchScore || 0 }}%</span>
</div>
</div>
<!-- 岗位职责 -->
<div v-if="jobDetail.description" class="agent-job-drawer__section">
<h4 class="agent-job-drawer__section-title">岗位职责</h4>
<div class="agent-job-drawer__section-content">{{ jobDetail.description }}</div>
</div>
<!-- 任职要求 -->
<div v-if="jobDetail.requirement || (jobDetail.skillTags && jobDetail.skillTags.length)" class="agent-job-drawer__section">
<h4 class="agent-job-drawer__section-title">任职要求</h4>
<!-- 技能标签 -->
<div v-if="jobDetail.skillTags && jobDetail.skillTags.length" class="agent-job-drawer__tags">
<span v-for="tag in jobDetail.skillTags" :key="tag" class="agent-job-drawer__tag">{{ tag }}</span>
</div>
<div v-if="jobDetail.requirement" class="agent-job-drawer__section-content">{{ jobDetail.requirement }}</div>
</div>
<!-- 加分项 -->
<div v-if="jobDetail.bonus" class="agent-job-drawer__section">
<h4 class="agent-job-drawer__section-title agent-job-drawer__section-title--bonus">加分项{{ jobDetail.bonus }}</h4>
</div>
<!-- 公司概况 -->
<div v-if="jobDetail.companyName || jobDetail.companyShortName" class="agent-job-drawer__section">
<h4 class="agent-job-drawer__section-title">公司概况</h4>
<div class="agent-job-drawer__company">
<!-- 公司信息行 -->
<div class="agent-job-drawer__company-row">
<div class="agent-job-drawer__company-logo">
<img v-if="jobDetail.companyLogoUrl" :src="jobDetail.companyLogoUrl" :alt="jobDetail.companyShortName" />
<svg v-else viewBox="0 0 32 32" fill="none">
<rect width="32" height="32" rx="4" fill="#EDF9FA"/>
<path d="M8 12h16v12H8z" stroke="#52CAD1" stroke-width="1.5"/>
<path d="M12 12V9a2 2 0 012-2h4a2 2 0 012 2v3" stroke="#52CAD1" stroke-width="1.5"/>
</svg>
</div>
<div class="agent-job-drawer__company-info">
<span class="agent-job-drawer__company-name">{{ jobDetail.companyShortName || jobDetail.companyName }}</span>
<span class="agent-job-drawer__company-detail">
{{ [jobDetail.companyIndustryName, jobDetail.companyType, jobDetail.companyScale].filter(Boolean).join(' · ') }}
</span>
</div>
</div>
<!-- 公司简介 -->
<div v-if="jobDetail.companySummary || jobDetail.companyDescription" class="agent-job-drawer__company-desc">
{{ jobDetail.companySummary || jobDetail.companyDescription }}
</div>
</div>
</div>
</div>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue'
import { fetchJobDetail } from '@/api/jobs'
import type { JobDetailData } from '@/api/jobs'
/** 组件 Props */
const props = defineProps<{
/** 是否显示抽屉 */
visible: boolean
/** 岗位 ID */
jobId: string
/** 投递状态(用于控制添加按钮显示) */
applicationStatus?: number | null
}>()
/** 事件 */
const emit = defineEmits<{
/** 关闭抽屉 */
(e: 'back'): void
/** 添加岗位到待投递 */
(e: 'add', jobId: string): void
/** 从待投递移除岗位 */
(e: 'remove', jobId: string): void
/** 更新visible */
(e: 'update:visible', val: boolean): void
}>()
/** 加载状态 */
const loading = ref(false)
/** 岗位详情数据 */
const jobDetail = ref<(JobDetailData & { applicationStatus?: number | null }) | null>(null)
/** 监听 visible 变化来加载数据 */
watch(() => props.visible, (newVal) => {
if (newVal && props.jobId) {
loadDetail()
}
})
/** 监听 jobId 变化,如果面板已打开则重新加载 */
watch(() => props.jobId, (newVal) => {
if (props.visible && newVal) {
loadDetail()
}
})
/** 监听 applicationStatus prop 变化,同步到 jobDetail */
watch(() => props.applicationStatus, (newVal) => {
if (jobDetail.value) {
jobDetail.value.applicationStatus = newVal ?? null
}
})
/** 格式化经验要求 */
const formatExperience = computed(() => {
if (!jobDetail.value) return ''
const min = jobDetail.value.minExperience
if (min === 0 || min === undefined || min === null) return '经验不限'
return `${min}年以上`
})
/** 格式化招聘分类 */
const formatRecruitCategory = computed(() => {
if (!jobDetail.value) return ''
const map: Record<number, string> = { 0: '社招', 1: '校招', 2: '实习', 3: '其他' }
return map[jobDetail.value.recruitCategory] || ''
})
/** 格式化学历要求 */
const formatEducation = computed(() => {
if (!jobDetail.value) return ''
const map: Record<number, string> = { 0: '不限', 1: '大专', 2: '本科', 3: '硕士', 4: '博士' }
return map[jobDetail.value.education] || ''
})
/** 加载岗位详情 */
async function loadDetail() {
if (!props.jobId) return
loading.value = true
try {
const res = await fetchJobDetail(props.jobId)
if (res.code === '0' && res.data) {
jobDetail.value = { ...res.data, applicationStatus: props.applicationStatus ?? null }
}
} catch (e) {
console.error('[AgentJobPreviewPanel] 加载岗位详情失败', e)
} finally {
loading.value = false
}
}
/** 关闭抽屉 */
function handleBack() {
emit('back')
emit('update:visible', false)
}
</script>
<style scoped lang="scss">
@use '../assets/styles/components/agent-job-preview-panel';
</style>