212 lines
8.6 KiB
Vue
212 lines
8.6 KiB
Vue
<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>
|