255 lines
8.4 KiB
Vue
255 lines
8.4 KiB
Vue
<template>
|
||
<!-- 申请进度面板 — 右侧面板显示已投递岗位列表(带筛选、搜索、滚动分页) -->
|
||
<div class="agent-apply-progress-panel">
|
||
<!-- 顶部标题栏 -->
|
||
<div class="agent-apply-progress-panel__header">
|
||
<span class="agent-apply-progress-panel__title">申请进度</span>
|
||
<!-- 关闭按钮 -->
|
||
<button class="agent-apply-progress-panel__close-btn" @click="emit('close')">
|
||
<svg viewBox="0 0 24 24" fill="none">
|
||
<circle cx="12" cy="12" r="10" fill="#1A1A2E" />
|
||
<path d="M8 8l8 8M16 8l-8 8" stroke="#fff" stroke-width="1.5" stroke-linecap="round" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 筛选栏:状态下拉 + 搜索框 -->
|
||
<div class="agent-apply-progress-panel__filter">
|
||
<!-- 状态下拉选择 -->
|
||
<div class="agent-apply-progress-panel__select-wrap">
|
||
<select v-model="selectedStatus" class="agent-apply-progress-panel__select" @change="handleFilterChange">
|
||
<option :value="null">全部</option>
|
||
<option :value="0">已投递</option>
|
||
<option :value="1">面试中</option>
|
||
<option :value="2">有Offer</option>
|
||
<option :value="3">未通过</option>
|
||
<option :value="4">已结束</option>
|
||
</select>
|
||
</div>
|
||
<!-- 搜索框 -->
|
||
<input
|
||
v-model="keyword"
|
||
class="agent-apply-progress-panel__search"
|
||
placeholder="搜索岗位/公司"
|
||
@keyup.enter="handleSearch"
|
||
/>
|
||
</div>
|
||
|
||
<!-- 列表内容(可滚动,触底加载更多) -->
|
||
<div ref="listRef" class="agent-apply-progress-panel__list" @scroll="handleScroll">
|
||
<!-- 岗位项 -->
|
||
<div
|
||
v-for="job in jobList"
|
||
:key="job.id"
|
||
class="agent-apply-progress-panel__item"
|
||
>
|
||
<!-- 左侧信息区域 -->
|
||
<div class="agent-apply-progress-panel__info">
|
||
<!-- 公司 Logo -->
|
||
<div class="agent-apply-progress-panel__logo">
|
||
<svg viewBox="0 0 24 24" fill="none">
|
||
<rect x="3" y="7" width="18" height="14" rx="2" stroke="currentColor" stroke-width="1.5"/>
|
||
<path d="M7 7V5a2 2 0 012-2h6a2 2 0 012 2v2" stroke="currentColor" stroke-width="1.5"/>
|
||
</svg>
|
||
</div>
|
||
<!-- 岗位详情 -->
|
||
<div class="agent-apply-progress-panel__detail">
|
||
<div class="agent-apply-progress-panel__company">{{ job.companyShortName || job.companyName }}</div>
|
||
<div class="agent-apply-progress-panel__position">{{ job.title }}</div>
|
||
<!-- 标签 -->
|
||
<div class="agent-apply-progress-panel__tags">
|
||
<span v-if="job.regionName" class="agent-apply-progress-panel__tag">{{ job.regionName }}</span>
|
||
<span v-if="job.categoryName" class="agent-apply-progress-panel__tag">{{ job.categoryName }}</span>
|
||
<span v-for="tag in (job.tags || []).slice(0, 2)" :key="tag" class="agent-apply-progress-panel__tag">{{ tag }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右侧:匹配度 + 状态下拉 + 删除按钮 -->
|
||
<div class="agent-apply-progress-panel__right">
|
||
<!-- 匹配度环形 -->
|
||
<div class="agent-apply-progress-panel__score">
|
||
<svg class="agent-apply-progress-panel__ring" viewBox="0 0 44 44">
|
||
<circle cx="22" cy="22" r="18" fill="none" stroke="#E8E8E8" stroke-width="3" />
|
||
<circle
|
||
cx="22" cy="22" r="18" fill="none"
|
||
stroke="#4FC2C9" stroke-width="3"
|
||
stroke-linecap="round"
|
||
:stroke-dasharray="2 * Math.PI * 18"
|
||
:stroke-dashoffset="2 * Math.PI * 18 * (1 - (job.matchScore || 0) / 100)"
|
||
transform="rotate(-90 22 22)"
|
||
/>
|
||
</svg>
|
||
<span class="agent-apply-progress-panel__score-text">{{ job.matchScore || 0 }}%</span>
|
||
</div>
|
||
<!-- 状态下拉 -->
|
||
<select
|
||
class="agent-apply-progress-panel__status-select"
|
||
:value="job.status"
|
||
@change="handleStatusChange(job, $event)"
|
||
>
|
||
<option :value="0">已投递</option>
|
||
<option :value="1">面试中</option>
|
||
<option :value="2">有Offer</option>
|
||
<option :value="3">未通过</option>
|
||
<option :value="4">已结束</option>
|
||
</select>
|
||
<!-- 删除按钮 -->
|
||
<button class="agent-apply-progress-panel__delete-btn" @click="handleDelete(job)">
|
||
<svg viewBox="0 0 16 16" fill="none">
|
||
<circle cx="8" cy="8" r="7" fill="#BFBFBF" />
|
||
<path d="M5.5 5.5l5 5M10.5 5.5l-5 5" stroke="#fff" stroke-width="1.2" stroke-linecap="round" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 加载更多提示 -->
|
||
<div v-if="loadingMore" class="agent-apply-progress-panel__loading">加载中...</div>
|
||
<!-- 没有更多数据 -->
|
||
<div v-if="noMore && jobList.length > 0" class="agent-apply-progress-panel__no-more">暂无更多数据</div>
|
||
<!-- 空状态 -->
|
||
<div v-if="!loading && jobList.length === 0 && !loadingMore" class="agent-apply-progress-panel__empty">暂无数据</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref, onMounted } from 'vue'
|
||
import { fetchApplyList } from '@/api/jobs'
|
||
import { applyJob, cancelApplyJob } from '@/api/agent'
|
||
import type { JobListItem } from '@/api/jobs'
|
||
|
||
/** 事件 */
|
||
const emit = defineEmits<{
|
||
/** 关闭面板 */
|
||
(e: 'close'): void
|
||
}>()
|
||
|
||
/** 岗位列表数据 */
|
||
const jobList = ref<JobListItem[]>([])
|
||
|
||
/** 当前页码 */
|
||
const currentPage = ref(1)
|
||
|
||
/** 每页条数 */
|
||
const pageSize = 30
|
||
|
||
/** 是否正在加载首页 */
|
||
const loading = ref(false)
|
||
|
||
/** 是否正在加载更多 */
|
||
const loadingMore = ref(false)
|
||
|
||
/** 是否没有更多数据 */
|
||
const noMore = ref(false)
|
||
|
||
/** 当前选中的状态筛选 */
|
||
const selectedStatus = ref<number | null>(null)
|
||
|
||
/** 搜索关键词 */
|
||
const keyword = ref('')
|
||
|
||
/** 列表容器 ref */
|
||
const listRef = ref<HTMLElement | null>(null)
|
||
|
||
/** 加载投递列表 */
|
||
async function loadList(page: number) {
|
||
if (page === 1) {
|
||
loading.value = true
|
||
} else {
|
||
loadingMore.value = true
|
||
}
|
||
try {
|
||
const res = await fetchApplyList({
|
||
pageNum: page,
|
||
pageSize,
|
||
status: selectedStatus.value,
|
||
keyword: keyword.value || undefined,
|
||
})
|
||
if (res.code === '0' && res.data) {
|
||
const list = res.data.list || []
|
||
if (page === 1) {
|
||
jobList.value = list
|
||
} else {
|
||
jobList.value.push(...list)
|
||
}
|
||
/* 判断是否还有更多数据 */
|
||
const total = Number(res.data.total || 0)
|
||
if (jobList.value.length >= total || list.length < pageSize) {
|
||
noMore.value = true
|
||
}
|
||
}
|
||
} catch {
|
||
console.error('[AgentApplyProgressPanel] 加载投递列表失败')
|
||
} finally {
|
||
loading.value = false
|
||
loadingMore.value = false
|
||
}
|
||
}
|
||
|
||
/** 重置列表并重新加载 */
|
||
function resetAndLoad() {
|
||
currentPage.value = 1
|
||
jobList.value = []
|
||
noMore.value = false
|
||
loadList(1)
|
||
}
|
||
|
||
/** 筛选状态变更 */
|
||
function handleFilterChange() {
|
||
resetAndLoad()
|
||
}
|
||
|
||
/** 搜索框回车 */
|
||
function handleSearch() {
|
||
resetAndLoad()
|
||
}
|
||
|
||
/** 滚动触底加载更多 */
|
||
function handleScroll() {
|
||
if (!listRef.value || loadingMore.value || noMore.value) return
|
||
const { scrollTop, scrollHeight, clientHeight } = listRef.value
|
||
if (scrollTop + clientHeight >= scrollHeight - 50) {
|
||
currentPage.value++
|
||
loadList(currentPage.value)
|
||
}
|
||
}
|
||
|
||
/** 修改单个岗位的投递状态 */
|
||
async function handleStatusChange(job: JobListItem, event: Event) {
|
||
const newStatus = Number((event.target as HTMLSelectElement).value)
|
||
try {
|
||
await applyJob({ jobId: job.id, status: newStatus })
|
||
job.status = newStatus
|
||
ElMessage.success('状态已更新')
|
||
} catch {
|
||
ElMessage.error('状态更新失败')
|
||
/* 恢复原值 — 触发视图刷新 */
|
||
;(event.target as HTMLSelectElement).value = String(job.status)
|
||
}
|
||
}
|
||
|
||
/** 删除(取消投递) */
|
||
async function handleDelete(job: JobListItem) {
|
||
try {
|
||
await cancelApplyJob(job.id)
|
||
const idx = jobList.value.findIndex(j => j.id === job.id)
|
||
if (idx !== -1) {
|
||
jobList.value.splice(idx, 1)
|
||
}
|
||
ElMessage.success('已删除')
|
||
} catch {
|
||
ElMessage.error('删除失败')
|
||
}
|
||
}
|
||
|
||
onMounted(() => {
|
||
loadList(1)
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
@use '../assets/styles/components/agent-apply-progress-panel';
|
||
</style>
|