AI助手引导步骤返回上一步和正式使用AI助手时的再次设置功能,和投递过程的其他地方按钮点击保护

This commit is contained in:
2026-05-28 11:09:38 +08:00
parent a9638fc7ec
commit f52a7c56f7
21 changed files with 2296 additions and 47 deletions
+254
View File
@@ -0,0 +1,254 @@
<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>
+8
View File
@@ -10,6 +10,7 @@
:key="job.id"
class="agent-chat-job-list__item"
v-if="displayJobs.length>0"
@click="handleClickJob(job)"
>
<!-- 左侧公司图标 + 岗位信息 -->
<div class="agent-chat-job-list__info">
@@ -79,6 +80,8 @@ const props = defineProps<{
const emit = defineEmits<{
/** 点击查看全部岗位 */
(e: 'viewAll'): void
/** 点击岗位查看详情 */
(e: 'clickJob', job: AgentRecommendJob): void
}>()
/** 只显示前3个岗位 */
@@ -88,6 +91,11 @@ const displayJobs = computed(() => props.jobs.slice(0, 3))
function handleViewAll() {
emit('viewAll')
}
/** 点击岗位 — 通知父组件打开岗位预览 */
function handleClickJob(job: AgentRecommendJob) {
emit('clickJob', job)
}
</script>
<style scoped lang="scss">
+197
View File
@@ -0,0 +1,197 @@
<template>
<!-- Agent岗位预览面板 右侧面板显示岗位详情 -->
<div class="agent-job-preview-panel" v-loading="loading" element-loading-text="加载中...">
<!-- 顶部标题栏 -->
<div class="agent-job-preview-panel__header">
<div class="agent-job-preview-panel__back" @click="handleBack">
<svg viewBox="0 0 16 16" fill="none" class="agent-job-preview-panel__back-icon">
<path d="M10 3L5 8l5 5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span>岗位详情</span>
</div>
<!-- 添加按钮 仅未投递时显示 -->
<button
v-if="jobDetail && (jobDetail.applicationStatus === null || jobDetail.applicationStatus === undefined)"
class="agent-job-preview-panel__add-btn"
:disabled="addLoading"
@click="handleAdd"
>+ 添加</button>
<!-- 移出按钮 仅待投递状态时显示 -->
<button
v-else-if="jobDetail && jobDetail.applicationStatus === -1"
class="agent-job-preview-panel__remove-btn"
:disabled="addLoading"
@click="handleRemove"
>移出</button>
</div>
<!-- 岗位详情内容 -->
<div v-if="jobDetail" class="agent-job-preview-panel__body">
<!-- 岗位卡片 -->
<div class="agent-job-preview-panel__card">
<div class="agent-job-preview-panel__card-top">
<!-- 左侧信息 -->
<div class="agent-job-preview-panel__card-left">
<div class="agent-job-preview-panel__company-row">
<div class="agent-job-preview-panel__company-icon">
<img v-if="jobDetail.companyLogoUrl" :src="jobDetail.companyLogoUrl" :alt="jobDetail.companyShortName" />
<svg v-else 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>
<span class="agent-job-preview-panel__company-name">{{ jobDetail.companyShortName || jobDetail.companyName }}</span>
<span class="agent-job-preview-panel__time">{{ formatTime }}</span>
</div>
<h3 class="agent-job-preview-panel__job-title">{{ jobDetail.jobTitle }}</h3>
<!-- 岗位元信息 -->
<div class="agent-job-preview-panel__meta">
<span v-if="jobDetail.regionName" class="agent-job-preview-panel__meta-item">
<svg viewBox="0 0 16 16" fill="none" class="agent-job-preview-panel__meta-icon">
<circle cx="8" cy="6.5" r="2.5" stroke="currentColor" stroke-width="1.2"/>
<path d="M8 14s-5-4-5-7.5a5 5 0 0110 0C13 10 8 14 8 14z" stroke="currentColor" stroke-width="1.2"/>
</svg>
{{ jobDetail.regionName }}
</span>
<span class="agent-job-preview-panel__meta-item">
<svg viewBox="0 0 16 16" fill="none" class="agent-job-preview-panel__meta-icon">
<rect x="2" y="3" width="12" height="11" rx="1.5" stroke="currentColor" stroke-width="1.2"/>
<path d="M2 6.5h12" stroke="currentColor" stroke-width="1.2"/>
</svg>
{{ formatEmploymentType(jobDetail.employmentType) }}
</span>
<span v-if="jobDetail.salary" class="agent-job-preview-panel__meta-item">
<svg viewBox="0 0 16 16" fill="none" class="agent-job-preview-panel__meta-icon">
<path d="M8 1v14M4 4h8M3 8h10M5 12h6" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/>
</svg>
{{ jobDetail.salary }}
</span>
</div>
</div>
<!-- 右侧匹配度环 -->
<div class="agent-job-preview-panel__match">
<svg viewBox="0 0 60 60" class="agent-job-preview-panel__ring-svg">
<circle cx="30" cy="30" r="25" stroke-width="4" stroke="#E8E8E8" fill="none" />
<circle cx="30" cy="30" r="25" stroke-width="4" fill="none"
stroke="#4FC2C9" stroke-linecap="round"
:stroke-dasharray="2 * Math.PI * 25"
:stroke-dashoffset="2 * Math.PI * 25 * (1 - (jobDetail.matchScore || 0) / 100)"
transform="rotate(-90 30 30)"
/>
</svg>
<span class="agent-job-preview-panel__match-text">{{ jobDetail.matchScore || 0 }}%</span>
</div>
</div>
</div>
<!-- 岗位职责 -->
<div v-if="jobDetail.description" class="agent-job-preview-panel__section">
<h4 class="agent-job-preview-panel__section-title">岗位职责</h4>
<div class="agent-job-preview-panel__section-content">{{ jobDetail.description }}</div>
</div>
<!-- 任职要求 -->
<div v-if="jobDetail.requirement" class="agent-job-preview-panel__section">
<h4 class="agent-job-preview-panel__section-title">任职要求</h4>
<div class="agent-job-preview-panel__section-content">{{ jobDetail.requirement }}</div>
</div>
<!-- 加分项 -->
<div v-if="jobDetail.bonus" class="agent-job-preview-panel__section">
<h4 class="agent-job-preview-panel__section-title">加分项</h4>
<div class="agent-job-preview-panel__section-content">{{ jobDetail.bonus }}</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { fetchJobDetail } from '@/api/jobs'
import type { JobDetailData } from '@/api/jobs'
/** 组件 Props */
const props = defineProps<{
/** 岗位 ID */
jobId: string
/** 投递状态(用于控制添加按钮显示) */
applicationStatus?: number | null
}>()
/** 事件 */
const emit = defineEmits<{
/** 返回上一个面板模式 */
(e: 'back'): void
/** 添加岗位到待投递 */
(e: 'add', jobId: string): void
/** 从待投递移除岗位 */
(e: 'remove', jobId: string): void
}>()
/** 加载状态 */
const loading = ref(false)
/** 添加按钮加载状态 */
const addLoading = ref(false)
/** 岗位详情数据(扩展 applicationStatus 字段) */
const jobDetail = ref<(JobDetailData & { applicationStatus?: number | null }) | null>(null)
/** 监听 applicationStatus prop 变化,同步到 jobDetail */
watch(() => props.applicationStatus, (newVal) => {
if (jobDetail.value) {
jobDetail.value.applicationStatus = newVal ?? null
}
})
/** 格式化时间显示 */
const formatTime = computed(() => {
// 暂时不显示具体时间,可后续扩展
return ''
})
/** 工作类型映射 */
function formatEmploymentType(type: number | undefined): string {
const map: Record<number, string> = { 0: '全职', 1: '兼职' }
return map[type ?? -1] ?? ''
}
/** 加载岗位详情 */
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')
}
/** 添加到待投递 */
function handleAdd() {
emit('add', props.jobId)
}
/** 从待投递移除 */
function handleRemove() {
emit('remove', props.jobId)
}
onMounted(() => {
loadDetail()
})
</script>
<style scoped lang="scss">
@use '../assets/styles/components/agent-job-preview-panel';
</style>
+8 -1
View File
@@ -27,7 +27,7 @@
class="agent-match-job-add__item"
>
<!-- 左侧公司图标 + 岗位信息 -->
<div class="agent-match-job-add__info">
<div class="agent-match-job-add__info" @click="handleClickJob(job)">
<!-- 公司 Logo -->
<div class="agent-match-job-add__logo">
<img v-if="job.companyLogoUrl" :src="job.companyLogoUrl" :alt="job.companyShortName" />
@@ -116,6 +116,8 @@ const emit = defineEmits<{
(e: 'toggle', job: AgentRecommendJob): void
/** 全部添加操作 */
(e: 'addAll'): void
/** 点击岗位查看详情 */
(e: 'clickJob', job: AgentRecommendJob): void
}>()
/** 点击添加/移除 — 通知父组件处理 */
@@ -123,6 +125,11 @@ function handleToggle(job: AgentRecommendJob) {
emit('toggle', job)
}
/** 点击岗位 — 通知父组件打开岗位预览 */
function handleClickJob(job: AgentRecommendJob) {
emit('clickJob', job)
}
/** 全部添加 — 通知父组件处理(只传 applicationStatus 为 null 的岗位) */
function handleAddAll() {
emit('addAll')
+188
View File
@@ -0,0 +1,188 @@
<template>
<!-- 待投递岗位列表面板 右侧面板显示全部待投递岗位滚动分页 -->
<div class="agent-pending-job-list-panel">
<!-- 顶部标题栏 -->
<div class="agent-pending-job-list-panel__header">
<span class="agent-pending-job-list-panel__title">待投递岗位列表</span>
<!-- 关闭按钮 -->
<button class="agent-pending-job-list-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 ref="listRef" class="agent-pending-job-list-panel__list" @scroll="handleScroll">
<!-- 岗位项 -->
<div
v-for="job in jobList"
:key="job.id"
class="agent-pending-job-list-panel__item"
>
<!-- 左侧信息区域 -->
<div class="agent-pending-job-list-panel__info">
<!-- 公司 Logo -->
<div class="agent-pending-job-list-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-pending-job-list-panel__detail">
<div class="agent-pending-job-list-panel__company">{{ job.companyShortName || job.companyName }}</div>
<div class="agent-pending-job-list-panel__position">{{ job.title }}</div>
<!-- 标签 -->
<div class="agent-pending-job-list-panel__tags">
<span v-if="job.regionName" class="agent-pending-job-list-panel__tag">{{ job.regionName }}</span>
<span v-if="job.categoryName" class="agent-pending-job-list-panel__tag">{{ job.categoryName }}</span>
<span v-for="tag in (job.tags || []).slice(0, 2)" :key="tag" class="agent-pending-job-list-panel__tag">{{ tag }}</span>
</div>
</div>
</div>
<!-- 右侧匹配度 + 移出按钮 -->
<div class="agent-pending-job-list-panel__right">
<!-- 匹配度环形 -->
<div class="agent-pending-job-list-panel__score">
<svg class="agent-pending-job-list-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-pending-job-list-panel__score-text">{{ job.matchScore || 0 }}%</span>
</div>
<!-- 移出按钮 -->
<button
class="agent-pending-job-list-panel__remove-btn"
:disabled="removingIds.includes(job.id)"
@click="handleRemove(job)"
>移出</button>
</div>
</div>
<!-- 加载更多提示 -->
<div v-if="loadingMore" class="agent-pending-job-list-panel__loading">加载中...</div>
<!-- 没有更多数据 -->
<div v-if="noMore && jobList.length > 0" class="agent-pending-job-list-panel__no-more">暂无更多数据</div>
<!-- 空状态 -->
<div v-if="!loading && jobList.length === 0" class="agent-pending-job-list-panel__empty">暂无待投递岗位</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { fetchAgentTaskList } from '@/api/jobs'
import { cancelApplyJob } from '@/api/agent'
import type { JobListItem } from '@/api/jobs'
/** 事件 */
const emit = defineEmits<{
/** 关闭面板 */
(e: 'close'): void
/** 移除岗位后通知父组件 */
(e: 'removed', jobId: string): void
}>()
/** 岗位列表数据 */
const jobList = ref<JobListItem[]>([])
/** 当前页码 */
const currentPage = ref(1)
/** 每页条数 */
const pageSize = 30
/** 是否正在加载首页 */
const loading = ref(false)
/** 是否正在加载更多 */
const loadingMore = ref(false)
/** 是否没有更多数据 */
const noMore = ref(false)
/** 正在移除中的岗位 ID 列表 */
const removingIds = ref<string[]>([])
/** 列表容器 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 fetchAgentTaskList({ pageNum: page, pageSize, tab: 1 })
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('[AgentPendingJobListPanel] 加载待投递列表失败')
} finally {
loading.value = false
loadingMore.value = false
}
}
/** 滚动触底加载更多 */
function handleScroll() {
if (!listRef.value || loadingMore.value || noMore.value) return
const { scrollTop, scrollHeight, clientHeight } = listRef.value
/* 距离底部 50px 时触发加载 */
if (scrollTop + clientHeight >= scrollHeight - 50) {
currentPage.value++
loadList(currentPage.value)
}
}
/** 移出岗位 */
async function handleRemove(job: JobListItem) {
if (removingIds.value.includes(job.id)) return
removingIds.value.push(job.id)
try {
await cancelApplyJob(job.id)
/* 从列表中移除 */
const idx = jobList.value.findIndex(j => j.id === job.id)
if (idx !== -1) {
jobList.value.splice(idx, 1)
}
emit('removed', job.id)
ElMessage.success('已移除')
} catch {
ElMessage.error('移除失败,请重试')
} finally {
removingIds.value = removingIds.value.filter(id => id !== job.id)
}
}
onMounted(() => {
loadList(1)
})
</script>
<style scoped lang="scss">
@use '../assets/styles/components/agent-pending-job-list-panel';
</style>
+438
View File
@@ -0,0 +1,438 @@
<template>
<!-- Agent设置面板 右侧面板 -->
<div class="agent-setting-panel">
<!-- 顶部标题栏 -->
<div class="agent-setting-panel__header">
<span class="agent-setting-panel__title">设置</span>
<!-- 关闭按钮 -->
<button class="agent-setting-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-setting-panel__body">
<!-- ========== 个人资料设置项 ========== -->
<div class="agent-setting-panel__section">
<!-- 个人资料标题行标题 + 编辑按钮含展开/收起箭头 -->
<div class="agent-setting-panel__section-header" @click="toggleProfileExpand">
<span class="agent-setting-panel__section-title">个人资料</span>
<div class="agent-setting-panel__section-action">
<span class="agent-setting-panel__section-action-text">{{ profileExpanded ? '收起' : '编辑' }}</span>
<!-- 方向箭头展开时朝上收起时朝下 -->
<svg
viewBox="0 0 16 16"
fill="none"
class="agent-setting-panel__arrow"
:class="{ 'agent-setting-panel__arrow--up': profileExpanded }"
>
<path d="M4 6l4 4 4-4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</div>
<!-- 收起时的简要信息预览 -->
<div v-if="!profileExpanded" class="agent-setting-panel__section-summary">
<span v-if="profile.name">{{ profile.name }}</span>
<span v-if="profile.phone"> · {{ profile.phone }}</span>
<span v-if="profile.email"> · {{ profile.email }}</span>
<span v-if="!profile.name && !profile.phone && !profile.email" class="agent-setting-panel__section-empty">暂未填写个人资料</span>
</div>
<!-- 展开时显示 ProfilePageContent 组件 -->
<div v-if="profileExpanded" class="agent-setting-panel__section-content">
<ProfilePageContent :profile="profile" @edit="handleEdit" />
</div>
</div>
<!-- ========== 其他设置项待设计稿补充 ========== -->
<div class="agent-setting-panel__section">
<!-- 求职目标标题行 + 编辑按钮 -->
<div class="agent-setting-panel__section-header" @click="showJobGoalDialog = true">
<span class="agent-setting-panel__section-title">求职目标</span>
<div class="agent-setting-panel__section-action">
<span class="agent-setting-panel__section-action-text">编辑</span>
<svg viewBox="0 0 16 16" fill="none" class="agent-setting-panel__edit-icon">
<path d="M11.5 2.5l2 2L5 13H3v-2l8.5-8.5z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round"/>
</svg>
</div>
</div>
<!-- 求职意向标签展示 -->
<div class="agent-setting-panel__goal-tags">
<span v-for="name in intentionCategoryNames" :key="'cat-' + name" class="agent-setting-panel__goal-tag">{{ name }}</span>
<span v-for="name in intentionIndustryNames" :key="'ind-' + name" class="agent-setting-panel__goal-tag">{{ name }}</span>
<span v-for="name in intentionRegionNames" :key="'reg-' + name" class="agent-setting-panel__goal-tag">{{ name }}</span>
<span class="agent-setting-panel__goal-tag">{{ intentionEmploymentLabel }}</span>
<!-- 无意向时的空状态 -->
</div>
</div>
<div class="agent-setting-panel__section">
<div class="agent-setting-panel__section-header">
<span class="agent-setting-panel__section-title">浏览器插件</span>
</div>
<!-- 浏览器按钮列表 -->
<div class="agent-setting-panel__browser-btns">
<button
v-for="b in browserList"
:key="b.key"
class="agent-setting-panel__browser-btn"
@click="openBrowserGuide(b.key)"
>{{ b.label }}</button>
</div>
<!-- 下载插件按钮 -->
<div class="agent-setting-panel__download-wrap">
<button class="agent-setting-panel__download-btn" @click="downloadExtension">下载插件</button>
</div>
</div>
<div class="agent-setting-panel__section">
<div class="agent-setting-panel__section-header">
<span class="agent-setting-panel__section-title">求职助手配置</span>
</div>
</div>
</div>
<!-- 个人资料编辑抽屉 -->
<ProfileEditDrawer
v-model="showEditDrawer"
:module="editModule"
:initial-data="editInitialData"
:saving="saving"
@save="handleSaveEdit"
/>
<!-- 求职目标设置弹窗 -->
<JobGoalDialog v-model="showJobGoalDialog" />
<!-- 浏览器安装指引弹窗 -->
<el-dialog v-model="showBrowserGuide" :title="currentBrowserLabel + ' 安装指引'" width="80%" top="5vh" destroy-on-close>
<el-carousel :autoplay="false" indicator-position="outside" height="70vh" arrow="always">
<el-carousel-item v-for="(img, i) in currentBrowserImages" :key="i">
<div class="agent-setting-panel__guide-slide">
<img :src="img" :alt="currentBrowserLabel + ' 安装步骤 ' + (i + 1)" />
</div>
</el-carousel-item>
</el-carousel>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useStore } from 'vuex'
import ProfilePageContent from '@/components/ProfilePageContent.vue'
import ProfileEditDrawer from '@/components/ProfileEditDrawer.vue'
import JobGoalDialog from '@/components/JobGoalDialog.vue'
import {
saveProfile, saveEducation, saveWork, saveInternship, saveProject, saveCompetition,
fetchProfile, fetchEducation, fetchWork, fetchInternship, fetchProject, fetchCompetition
} from '@/api/profile'
import type { SaveEducationItem, SaveWorkItem, SaveProjectItem, SaveCompetitionItem } from '@/api/profile'
import { resolveRegionName } from '@/utils/region'
import { resolveIndustryName } from '@/utils/industry'
import { resolveJobCategoryName } from '@/utils/jobCategory'
/** 事件 */
const emit = defineEmits<{
/** 关闭面板 */
(e: 'close'): void
}>()
const store = useStore()
// ==================== 个人资料展开/收起状态 ====================
/** 个人资料区域是否展开 */
const profileExpanded = ref(false)
/** 切换个人资料展开/收起 */
function toggleProfileExpand() {
profileExpanded.value = !profileExpanded.value
}
// ==================== 求职目标 ====================
/** 求职目标设置弹窗显隐 */
const showJobGoalDialog = ref(false)
/** 求职意向 — 岗位类型名称列表 */
const intentionCategoryNames = computed(() => (store.state.jobIntention?.categoryIds || []).map((id: number) => resolveJobCategoryName(id)).filter(Boolean))
/** 求职意向 — 行业名称列表 */
const intentionIndustryNames = computed(() => (store.state.jobIntention?.industryIds || []).map((id: number) => resolveIndustryName(id)).filter(Boolean))
/** 求职意向 — 地区名称列表 */
const intentionRegionNames = computed(() => (store.state.jobIntention?.regionCodes || []).map((code: string) => resolveRegionName(code)).filter(Boolean))
/** 求职意向 — 就业类型文案 */
const intentionEmploymentLabel = computed(() => store.state.jobIntention?.employmentType === 1 ? '实习' : '全职')
// ==================== 浏览器插件 ====================
/** 插件下载地址 */
const extensionDownloadUrl = 'https://offerpie.oss-cn-guangzhou.aliyuncs.com/extension/chrome-mv3-prod.rar'
/** 浏览器列表 */
const browserList = [
{ key: 'chrome', label: 'Chrome浏览器' },
{ key: 'edge', label: 'Edge浏览器' },
{ key: 'safari', label: 'Safari浏览器' },
{ key: '360', label: '360浏览器' },
{ key: 'qq', label: 'QQ浏览器' },
]
/** 每个浏览器的指引图片数量 */
const browserImageCount: Record<string, number> = { chrome: 3, edge: 3, safari: 3, '360': 3, qq: 3 }
/** 指引图片 OSS 基础路径 */
const guidanceImageBase = 'http://offerpie.oss-cn-guangzhou.aliyuncs.com/extension/guidance_image'
/** 浏览器安装指引弹窗显隐 */
const showBrowserGuide = ref(false)
/** 当前选中的浏览器 key */
const currentBrowserKey = ref('')
/** 当前浏览器名称 */
const currentBrowserLabel = computed(() => browserList.find(b => b.key === currentBrowserKey.value)?.label || '')
/** 当前浏览器的指引图片列表 */
const currentBrowserImages = computed(() => {
const key = currentBrowserKey.value
if (!key) return []
const count = browserImageCount[key] || 3
return Array.from({ length: count }, (_, i) => `${guidanceImageBase}/guidance-image-${key}-${String(i + 1).padStart(2, '0')}.png`)
})
/** 打开浏览器安装指引弹窗 */
function openBrowserGuide(key: string) {
currentBrowserKey.value = key
showBrowserGuide.value = true
}
/** 下载插件 */
function downloadExtension() {
window.open(extensionDownloadUrl, '_blank')
}
// ==================== 个人资料数据 ====================
/** 个人档案响应式数据 */
const profile = ref({
name: '', phone: '', email: '', idNumber: '', regionCode: '', portfolioUrl: '', wechat: '',
skills: [] as string[], certificates: [] as string[],
education: [] as Array<{ school: string; major: string; studyType: number; degree: number; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }>,
works: [] as Array<{ companyName: string; position: string; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }>,
internships: [] as Array<{ companyName: string; position: string; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }>,
projects: [] as Array<{ projectName: string; companyName: string; role: string; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }>,
competitions: [] as Array<{ competitionName: string; award: string; awardDate: string; description: Array<{ id: string; text: string }> }>,
})
// ==================== 加载个人资料 ====================
onMounted(async () => {
if (!store.state.regions.length) store.dispatch('loadCommonData')
store.dispatch('loadJobIntention')
await loadProfile()
await loadEducation()
await loadWork()
await loadInternship()
await loadProject()
await loadCompetition()
})
/** 加载基本信息 */
async function loadProfile() {
try {
const res = await fetchProfile()
if (res.code === '0' && res.data) {
const d = res.data
profile.value.name = d.name || ''
profile.value.phone = d.mobileNumber || ''
profile.value.email = d.email || ''
profile.value.idNumber = d.idCard || ''
profile.value.regionCode = d.regionCode || ''
profile.value.wechat = d.wechatNumber || ''
profile.value.skills = d.skills || []
profile.value.certificates = d.certificates || []
profile.value.portfolioUrl = d.portfolioUrl || ''
}
} catch { console.error('[AgentSettingPanel] 加载个人资料失败') }
}
/** 加载教育经历 */
async function loadEducation() {
try {
const res = await fetchEducation()
if (res.code === '0' && res.data) {
profile.value.education = res.data.map(item => ({
school: item.school || '', major: item.major || '', studyType: item.studyType ?? 0,
degree: item.degree ?? 2, startDate: item.startDate || '', endDate: item.endDate || '',
description: (item.description || []).map(d => ({ id: d.id || '', text: d.text || '' }))
}))
}
} catch { console.error('[AgentSettingPanel] 加载教育经历失败') }
}
/** 加载工作经历 */
async function loadWork() {
try {
const res = await fetchWork()
if (res.code === '0' && res.data) {
profile.value.works = res.data.map(item => ({
companyName: item.companyName || '', position: item.position || '',
startDate: item.startDate || '', endDate: item.endDate || '',
description: (item.description || []).map(d => ({ id: d.id || '', text: d.text || '' }))
}))
}
} catch { console.error('[AgentSettingPanel] 加载工作经历失败') }
}
/** 加载实习经历 */
async function loadInternship() {
try {
const res = await fetchInternship()
if (res.code === '0' && res.data) {
profile.value.internships = res.data.map(item => ({
companyName: item.companyName || '', position: item.position || '',
startDate: item.startDate || '', endDate: item.endDate || '',
description: (item.description || []).map(d => ({ id: d.id || '', text: d.text || '' }))
}))
}
} catch { console.error('[AgentSettingPanel] 加载实习经历失败') }
}
/** 加载项目经历 */
async function loadProject() {
try {
const res = await fetchProject()
if (res.code === '0' && res.data) {
profile.value.projects = res.data.map(item => ({
projectName: item.projectName || '', companyName: item.companyName || '',
role: item.role || '', startDate: item.startDate || '', endDate: item.endDate || '',
description: (item.description || []).map(d => ({ id: d.id || '', text: d.text || '' }))
}))
}
} catch { console.error('[AgentSettingPanel] 加载项目经历失败') }
}
/** 加载竞赛经历 */
async function loadCompetition() {
try {
const res = await fetchCompetition()
if (res.code === '0' && res.data) {
profile.value.competitions = res.data.map(item => ({
competitionName: item.competitionName || '', award: item.award || '',
awardDate: item.awardDate || '',
description: (item.description || []).map(d => ({ id: d.id || '', text: d.text || '' }))
}))
}
} catch { console.error('[AgentSettingPanel] 加载竞赛经历失败') }
}
// ==================== 编辑抽屉 ====================
/** 编辑抽屉显隐 */
const showEditDrawer = ref(false)
/** 当前编辑的模块名 */
const editModule = ref('info')
/** 编辑抽屉初始数据 */
const editInitialData = ref<Record<string, any>>({})
/** 保存中状态 */
const saving = ref(false)
/** ProfilePageContent 点击编辑 — 打开对应模块的编辑抽屉 */
function handleEdit(section: string) {
editModule.value = section
if (section === 'info') {
editInitialData.value = { name: profile.value.name, email: profile.value.email, phone: profile.value.phone, location: profile.value.regionCode, wechat: profile.value.wechat }
} else if (section === 'education') {
editInitialData.value = { education: profile.value.education.map(edu => ({ ...edu, description: edu.description.map(d => ({ ...d })) })) }
} else if (section === 'work') {
editInitialData.value = { works: profile.value.works.map(exp => ({ ...exp, description: exp.description.map(d => ({ ...d })) })) }
} else if (section === 'internship') {
editInitialData.value = { internships: (profile.value.internships || []).map(exp => ({ ...exp, description: exp.description.map(d => ({ ...d })) })) }
} else if (section === 'project') {
editInitialData.value = { projects: (profile.value.projects || []).map(proj => ({ ...proj, description: proj.description.map(d => ({ ...d })) })) }
} else if (section === 'competition') {
editInitialData.value = { competitions: profile.value.competitions.map(comp => ({ ...comp, description: comp.description.map(d => ({ ...d })) })) }
} else if (section === 'portfolio') {
editInitialData.value = { portfolioUrl: profile.value.portfolioUrl }
} else if (section === 'skills') {
editInitialData.value = { skills: [...profile.value.skills] }
} else if (section === 'certificate') {
editInitialData.value = { certificates: [...(profile.value.certificates || [])] }
} else {
editInitialData.value = {}
}
showEditDrawer.value = true
}
/** 保存编辑数据 — 调用接口持久化 */
async function handleSaveEdit(data: Record<string, any>) {
saving.value = true
try {
if (editModule.value === 'info') {
await saveProfile({ name: data.name, email: data.email, mobileNumber: data.phone, regionCode: data.location, wechatNumber: data.wechat })
profile.value.name = data.name; profile.value.email = data.email
profile.value.phone = data.phone; profile.value.wechat = data.wechat
profile.value.regionCode = data.location || ''
ElMessage.success('个人信息保存成功')
} else if (editModule.value === 'education') {
const payload: SaveEducationItem[] = data.education.map((edu: any) => ({ school: edu.school, major: edu.major, degree: edu.degree, studyType: edu.studyType, startDate: edu.startDate, endDate: edu.endDate, description: edu.description.map((d: any) => ({ id: d.id, text: d.text })) }))
await saveEducation(payload)
profile.value.education = data.education.map((edu: any) => ({ school: edu.school, major: edu.major, studyType: edu.studyType, degree: edu.degree, startDate: edu.startDate, endDate: edu.endDate, description: edu.description.map((d: any) => ({ ...d })) }))
ElMessage.success('教育经历保存成功')
} else if (editModule.value === 'work') {
const payload: SaveWorkItem[] = data.works.map((w: any) => ({ companyName: w.companyName, position: w.position, startDate: w.startDate, endDate: w.endDate || '', description: w.description.map((d: any) => ({ id: d.id, text: d.text })) }))
await saveWork(payload)
profile.value.works = data.works.map((w: any) => ({ companyName: w.companyName, position: w.position, startDate: w.startDate, endDate: w.endDate, description: w.description.map((d: any) => ({ ...d })) }))
ElMessage.success('工作经历保存成功')
} else if (editModule.value === 'internship') {
const payload: SaveWorkItem[] = data.internships.map((i: any) => ({ companyName: i.companyName, position: i.position, startDate: i.startDate, endDate: i.endDate || '', description: i.description.map((d: any) => ({ id: d.id, text: d.text })) }))
await saveInternship(payload)
profile.value.internships = data.internships.map((i: any) => ({ companyName: i.companyName, position: i.position, startDate: i.startDate, endDate: i.endDate, description: i.description.map((d: any) => ({ ...d })) }))
ElMessage.success('实习经历保存成功')
} else if (editModule.value === 'project') {
const payload: SaveProjectItem[] = data.projects.map((p: any) => ({ projectName: p.projectName, companyName: p.companyName || '', role: p.role || '', startDate: p.startDate, endDate: p.endDate || '', description: p.description.map((d: any) => ({ id: d.id, text: d.text })) }))
await saveProject(payload)
profile.value.projects = data.projects.map((p: any) => ({ projectName: p.projectName, companyName: p.companyName, role: p.role, startDate: p.startDate, endDate: p.endDate, description: p.description.map((d: any) => ({ ...d })) }))
ElMessage.success('项目经历保存成功')
} else if (editModule.value === 'competition') {
const payload: SaveCompetitionItem[] = data.competitions.map((c: any) => ({ competitionName: c.competitionName, award: c.award || '', awardDate: c.awardDate || '', description: c.description.map((d: any) => ({ id: d.id, text: d.text })) }))
await saveCompetition(payload)
profile.value.competitions = data.competitions.map((c: any) => ({ competitionName: c.competitionName, award: c.award, awardDate: c.awardDate, description: c.description.map((d: any) => ({ ...d })) }))
ElMessage.success('竞赛经历保存成功')
} else if (editModule.value === 'portfolio') {
await saveProfile({ name: profile.value.name, email: profile.value.email, mobileNumber: profile.value.phone, regionCode: profile.value.regionCode, wechatNumber: profile.value.wechat, skills: profile.value.skills, certificates: profile.value.certificates, portfolioUrl: data.portfolioUrl })
profile.value.portfolioUrl = data.portfolioUrl
ElMessage.success('作品集保存成功')
} else if (editModule.value === 'skills') {
await saveProfile({ name: profile.value.name, email: profile.value.email, mobileNumber: profile.value.phone, regionCode: profile.value.regionCode, wechatNumber: profile.value.wechat, skills: [...data.skills], certificates: profile.value.certificates })
profile.value.skills = [...data.skills]
ElMessage.success('技能保存成功')
} else if (editModule.value === 'certificate') {
await saveProfile({ name: profile.value.name, email: profile.value.email, mobileNumber: profile.value.phone, regionCode: profile.value.regionCode, wechatNumber: profile.value.wechat, skills: profile.value.skills, certificates: [...data.certificates] })
profile.value.certificates = [...data.certificates]
ElMessage.success('证书保存成功')
}
} catch {
ElMessage.error('保存失败,请重试')
} finally {
saving.value = false
}
}
</script>
<style scoped lang="scss">
@use '../assets/styles/components/agent-setting-panel';
</style>
+34
View File
@@ -62,6 +62,11 @@
<!-- ========== 第2步确认目标 ========== -->
<template v-if="currentStep === 2">
<div class="agent-page__step2">
<!-- 返回上一步按钮 -->
<button class="agent-page__back-btn" @click="handleBack">
<svg viewBox="0 0 24 24" fill="none" width="16" height="16"><path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span>返回上一步</span>
</button>
<div class="agent-page__chat">
<div class="agent-page__chat-row">
<div class="agent-page__chat-avatar"><svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg></div>
@@ -119,6 +124,11 @@
<template v-if="currentStep === 3">
<!-- 上半部分网申常见问题 -->
<div v-if="step3Sub === 1" class="agent-page__step3">
<!-- 返回上一步按钮 -->
<button class="agent-page__back-btn" @click="handleBack">
<svg viewBox="0 0 24 24" fill="none" width="16" height="16"><path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span>返回上一步</span>
</button>
<div class="agent-page__left">
<div class="agent-page__intro-card">
<div class="agent-page__intro-header"><div class="agent-page__intro-icon"><svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg></div><h2 class="agent-page__intro-title">现在我们来开启自动投递吧</h2></div>
@@ -143,6 +153,11 @@
</div>
<!-- 下半部分插件安装 -->
<div v-if="step3Sub === 2" class="agent-page__step3">
<!-- 返回上一阶段按钮 -->
<button class="agent-page__back-btn" @click="handleBack">
<svg viewBox="0 0 24 24" fill="none" width="16" height="16"><path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span>返回上一步</span>
</button>
<div class="agent-page__left">
<div class="agent-page__intro-card">
<div class="agent-page__intro-header"><div class="agent-page__intro-icon"><svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg></div><h2 class="agent-page__intro-title">现在我们来开启自动投递吧</h2></div>
@@ -173,6 +188,11 @@
<!-- ========== 第4步配置求职助手 ========== -->
<template v-if="currentStep === 4">
<div v-if="!setupComplete" class="agent-page__step3">
<!-- 返回上一步按钮 -->
<button class="agent-page__back-btn" @click="handleBack">
<svg viewBox="0 0 24 24" fill="none" width="16" height="16"><path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>
<span>返回上一步</span>
</button>
<div class="agent-page__left">
<div class="agent-page__intro-card">
<div class="agent-page__intro-header"><div class="agent-page__intro-icon"><svg viewBox="0 0 24 24" fill="none"><path d="M12 12c2.7 0 5-2.3 5-5s-2.3-5-5-5-5 2.3-5 5 2.3 5 5 5zm0 2c-3.3 0-10 1.7-10 5v2h20v-2c0-3.3-6.7-5-10-5z" fill="currentColor"/></svg></div><h2 class="agent-page__intro-title">马上就好啦请选择你想要的投递模式</h2></div>
@@ -240,6 +260,20 @@ function handleNext() {
if (currentStep.value < steps.length) currentStep.value++
}
/** 返回上一步(含子阶段判断) */
function handleBack() {
if (currentStep.value === 3 && step3Sub.value === 2) {
// 第3步的第2阶段 → 回到第3步第1阶段
step3Sub.value = 1
} else if (currentStep.value === 4) {
// 第4步 → 回到第3步第2阶段(插件安装)
currentStep.value = 3
step3Sub.value = 2
} else if (currentStep.value > 1) {
currentStep.value--
}
}
// ==================== 编辑抽屉状态 ====================
const showEditDrawer = ref(false)
const editModule = ref('info')
+14 -3
View File
@@ -44,7 +44,7 @@
</template>
</div>
<!-- 查看全部 -->
<div class="agent-main__task-view-all" @click="emit('viewAll')">查看全部</div>
<div class="agent-main__task-view-all" @click="handleViewAll">查看全部</div>
</div>
</template>
@@ -66,8 +66,10 @@ const props = defineProps<{
const emit = defineEmits<{
/** 移除岗位后通知父组件刷新列表 */
(e: 'removed', jobId: string): void
/** 查看全部 */
/** 查看全部(进行中 tab */
(e: 'viewAll'): void
/** 查看全部(已完成 tab — 申请进度) */
(e: 'viewAllCompleted'): void
}>()
// ==================== 状态 ====================
@@ -102,7 +104,7 @@ function switchTab(tab: number) {
async function loadCompletedList() {
loading.value = true
try {
const res = await fetchAgentTaskList({ pageNum: 1, pageSize: 100, tab: 2 })
const res = await fetchAgentTaskList({ pageNum: 1, pageSize: 30, tab: 2 })
if (res.code === '0' && res.data) {
completedList.value = res.data.list || []
}
@@ -123,4 +125,13 @@ async function removeJob(job: JobListItem) {
ElMessage.error('移除失败')
}
}
/** 查看全部 — 根据当前 tab 触发不同事件 */
function handleViewAll() {
if (activeTab.value === 1) {
emit('viewAll')
} else {
emit('viewAllCompleted')
}
}
</script>
@@ -155,7 +155,7 @@ import { cancelAccount } from '@/api/auth'
const props = defineProps<{ modelValue: boolean }>()
/** 组件 Emits */
const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void }>()
const emit = defineEmits<{ (e: 'update:modelValue', value: boolean): void; (e: 'deleted'): void }>()
const router = useRouter()
const store = useStore()
@@ -225,9 +225,10 @@ const handleConfirmDelete = async () => {
}
}
/** 完成 — 关闭弹窗并退出登录 */
/** 完成 — 关闭弹窗,通知父组件注销完成,跳转首页 */
const handleFinish = () => {
emit('update:modelValue', false)
emit('deleted')
store.commit('SET_AUTHENTICATED', false)
router.push('/')
}
+6 -1
View File
@@ -240,7 +240,7 @@
<JobGoalDialog v-model="showGoalDialog" />
<!-- 注销账号弹窗 -->
<SettingsDeleteAccountDialog v-model="showDeleteAccount" />
<SettingsDeleteAccountDialog v-model="showDeleteAccount" @deleted="onAccountDeleted" />
<!-- 邀请注册送会员弹窗 -->
<SettingsInviteDialog v-model="showInviteDialog" />
@@ -385,6 +385,11 @@ const handleDeleteAccount = () => {
showDeleteAccount.value = true
}
/** 注销完成回调 — 关闭设置弹窗 */
const onAccountDeleted = () => {
emit('update:modelValue', false)
}
/** 管理订阅 */
const handleManageSubscription = () => {
ElMessage.info('管理订阅功能开发中')