个人资料教育经历学历类型保存bug,和岗位为列表页查询条件添加公司类型分类

This commit is contained in:
2026-07-17 16:28:50 +08:00
parent 27d2592d06
commit db46766d48
4 changed files with 141 additions and 7 deletions
+2 -1
View File
@@ -101,7 +101,8 @@ export interface JobListParams {
recruitCategory?: number
/** 排除岗位ID列表(用于推荐时排除已推荐过的) */
excludeJobIds?: number[]
/** 公司类型列表(上市企业、独角兽、国企等,可多选) */
companyTypes?: string[]
}
// ==================== 求职意向 ====================
+13 -2
View File
@@ -7,8 +7,8 @@
<div class="profile-edit-inline__fields-row">
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">学校</label><input class="profile-edit-inline__input" placeholder="请输入学校名称" v-model="form.school" /></div>
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">专业</label><input class="profile-edit-inline__input" placeholder="请输入专业名称" v-model="form.major" /></div>
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">学历</label><select class="profile-edit-inline__input" v-model="form.degree"><option value="大专">大专</option><option value="本科">本科</option><option value="硕士">硕士</option><option value="博士">博士</option></select></div>
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">学历类型</label><select class="profile-edit-inline__input" v-model="form.studyType"><option value="全日制">全日制</option><option value="非全日制">非全日制</option></select></div>
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">学历</label><select class="profile-edit-inline__input" v-model="form.degree"><option :value="1">大专</option><option :value="2">本科</option><option :value="3">硕士</option><option :value="4">博士</option></select></div>
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">学历类型</label><select class="profile-edit-inline__input" v-model="form.studyType"><option :value="0">全日制</option><option :value="1">非全日制</option></select></div>
<div class="profile-edit-inline__field-item"><label class="profile-edit-inline__label">就读时间</label>
<div class="profile-edit-inline__date-range">
<el-date-picker v-model="form.startDate" type="month" placeholder="开始" format="YYYY-MM" value-format="YYYY-MM" class="profile-edit-inline__date-picker" :teleported="true" popper-class="profile-drawer-date-popper" />
@@ -180,6 +180,17 @@ function initForm() {
aiPolishResult.value = ''
if (props.initialData) {
form.value = { ...props.initialData }
// 教育经历:确保 degree 和 studyType 为整数(兼容旧数据可能是中文字符串)
if (props.moduleType === 'education') {
const degreeMap: Record<string, number> = { '大专': 1, '本科': 2, '硕士': 3, '博士': 4 }
const studyTypeMap: Record<string, number> = { '全日制': 0, '非全日制': 1 }
if (typeof form.value.degree === 'string') {
form.value.degree = degreeMap[form.value.degree] ?? (Number(form.value.degree) || null)
}
if (typeof form.value.studyType === 'string') {
form.value.studyType = studyTypeMap[form.value.studyType] ?? (Number(form.value.studyType) || null)
}
}
// 将 description 数组拼接为文本
if (props.initialData.description?.length) {
descriptionText.value = props.initialData.description.map((d: DescriptionParagraph) => d.text).join('\n')
+124 -3
View File
@@ -63,9 +63,9 @@
:displayStyle="selectorDisplayStyle"
@update:industryIds="onIndustryChange"
/>
<!-- 其他筛选条件 -->
<!-- 招聘分类筛选条件 -->
<div
v-else
v-else-if="filter.key === 'jobType'"
class="jobs-page__filter-item"
@click="handleFilterClick(filter)"
>
@@ -73,7 +73,7 @@
<svg class="jobs-page__filter-arrow-icon" viewBox="0 0 12 12" fill="none">
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<!-- 工作类型下拉菜单 -->
<!-- 招聘分类下拉菜单 -->
<div
v-if="filter.key === 'jobType' && showJobTypeDropdown"
class="jobs-page__filter-dropdown"
@@ -90,6 +90,34 @@
</div>
</div>
</div>
<!-- 公司类型筛选条件 -->
<div
v-else-if="filter.key === 'companyType'"
class="jobs-page__filter-item"
@click.stop="handleFilterClick(filter)"
>
<span>{{ filter.selected || filter.label }}</span>
<svg class="jobs-page__filter-arrow-icon" viewBox="0 0 12 12" fill="none">
<path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<!-- 公司类型下拉菜单 -->
<div
v-if="showCompanyTypeDropdown"
class="jobs-page__filter-dropdown"
@click.stop
>
<div
class="jobs-page__filter-dropdown-item"
v-for="option in companyTypeOptions"
:key="option.label"
:class="{ 'jobs-page__filter-dropdown-item--active': filter.selected === option.label || (!filter.selected && !option.value.length) }"
@click.stop="selectCompanyType(filter, option)"
>
{{ option.label }}
</div>
</div>
</div>
</template>
</div>
<div class="jobs-page__search-box">
@@ -578,6 +606,7 @@ const filters = ref<FilterItem[]>([
{ label: '岗位', key: 'position', selected: '' },
{ label: '行业', key: 'industry', selected: '' },
{ label: '招聘分类', key: 'jobType', selected: '' },
{ label: '公司类型', key: 'companyType', selected: '' },
])
/** 招聘分类选项映射:从全局 store 常量统一引入 */
@@ -586,6 +615,54 @@ const jobTypeOptions = JOB_TYPE_OPTIONS
/** 招聘分类下拉菜单是否显示 */
const showJobTypeDropdown = ref(false)
/** 公司类型选项(label=显示文字,value=传给接口的字符串数组) */
const companyTypeOptions: { label: string; value: string[] }[] = [
{ label: '全部', value: [] },
{ label: '央国企', value: ['央企', '国企'] },
{ label: '独角兽', value: ['独角兽'] },
{ label: '事业单位', value: ['事业单位'] },
{ label: '上市企业', value: ['上市企业'] },
{ label: '外资企业', value: ['外资企业'] },
{ label: '合资企业', value: ['合资企业'] },
{ label: '民营企业', value: ['民营企业'] },
]
/** 公司类型下拉菜单是否显示 */
const showCompanyTypeDropdown = ref(false)
/** localStorage 缓存 key(带用户 ID 隔离不同用户) */
const COMPANY_TYPE_CACHE_KEY = 'offerpai_jobs_company_type_filter_'
/** 获取当前用户的公司类型缓存 key */
function getCompanyTypeCacheKey(): string {
const userId = store.state.userInfo?.id || 'anonymous'
return COMPANY_TYPE_CACHE_KEY + userId
}
/** 从 localStorage 读取缓存的公司类型筛选 */
function loadCachedCompanyTypes(): string[] {
try {
const cached = localStorage.getItem(getCompanyTypeCacheKey())
if (!cached || cached === 'null') return []
return JSON.parse(cached) as string[]
} catch {
return []
}
}
/** 保存公司类型筛选到 localStorage */
function saveCachedCompanyTypes(types: string[]) {
const key = getCompanyTypeCacheKey()
if (types.length === 0) {
localStorage.setItem(key, 'null')
} else {
localStorage.setItem(key, JSON.stringify(types))
}
}
/** 当前选中的公司类型(传给接口的字符串数组),onMounted 中从缓存恢复 */
const selectedCompanyTypes = ref<string[]>([])
/** 当前选中的招聘分类 — 直接读 store.jobIntention.recruitCategory */
const selectedEmploymentType = computed<number | null>(
() => store.state.jobIntention.recruitCategory ?? null,
@@ -738,6 +815,10 @@ function onRegionChange(codes: string[]) {
function handleFilterClick(filter: FilterItem) {
if (filter.key === 'jobType') {
showJobTypeDropdown.value = !showJobTypeDropdown.value
showCompanyTypeDropdown.value = false
} else if (filter.key === 'companyType') {
showCompanyTypeDropdown.value = !showCompanyTypeDropdown.value
showJobTypeDropdown.value = false
}
}
@@ -751,6 +832,15 @@ function selectJobType(filter: FilterItem, option: { label: string; value: numbe
})
}
/** 选中公司类型选项 */
function selectCompanyType(filter: FilterItem, option: { label: string; value: string[] }) {
filter.selected = option.value.length ? option.label : ''
showCompanyTypeDropdown.value = false
selectedCompanyTypes.value = option.value
saveCachedCompanyTypes(option.value)
reloadFirstPage()
}
/** 监听 store 中 recruitCategory 变化,同步招聘分类筛选按钮的显示文字 */
watch(selectedEmploymentType, (val) => {
const jobTypeFilter = filters.value.find(f => f.key === 'jobType')
@@ -765,6 +855,7 @@ function closeDropdownOnClickOutside(e: MouseEvent) {
const target = e.target as HTMLElement
if (!target.closest('.jobs-page__filter-item')) {
showJobTypeDropdown.value = false
showCompanyTypeDropdown.value = false
}
}
@@ -973,6 +1064,32 @@ async function handleApplyMethodAction() {
onMounted(async () => {
document.addEventListener('click', closeDropdownOnClickOutside)
// 从缓存恢复公司类型筛选按钮的显示文字
// 需要等 userInfo 加载后再读取(刷新时 userInfo 可能还没有)
const restoreCompanyTypeFilter = () => {
const cached = loadCachedCompanyTypes()
if (cached.length) {
selectedCompanyTypes.value = cached
const matched = companyTypeOptions.find(o => JSON.stringify(o.value) === JSON.stringify(cached))
const companyTypeFilter = filters.value.find(f => f.key === 'companyType')
if (companyTypeFilter && matched) {
companyTypeFilter.selected = matched.label
}
}
}
if (store.state.userInfo?.id) {
restoreCompanyTypeFilter()
} else {
// userInfo 还没加载,监听变化后恢复
const unwatch = watch(() => store.state.userInfo, (info) => {
if (info?.id) {
restoreCompanyTypeFilter()
unwatch()
}
})
}
// 窗口 resize 时更新尖角位置
window.addEventListener('resize', updateAiTipArrowPosition)
@@ -1161,6 +1278,10 @@ function buildParams(): JobListParams {
if (keyword.value.trim()) {
params.keyword = keyword.value.trim()
}
// 公司类型筛选
if (selectedCompanyTypes.value.length) {
params.companyTypes = selectedCompanyTypes.value
}
return params
}
+2 -1
View File
@@ -296,7 +296,7 @@
<template v-if="expEditingId !== edu.id">
<div class="resume-detail__exp-row">
<span class="resume-detail__exp-name">{{ edu.school }}</span>
<span v-if="edu.major || edu.degree" class="resume-detail__exp-role">{{ edu.major }}{{ edu.degree ? '·' + edu.degree : '' }}</span>
<span v-if="edu.major || edu.degree" class="resume-detail__exp-role">{{ edu.major }}{{ edu.degree ? '·' + (EDUCATION_MAP[Number(edu.degree)] || edu.degree) : '' }}</span>
<span v-if="hasDiagnosis && getIssueByRecord('education', edu.id!) && getIssueByRecord('education', edu.id!)!.status === 0" class="resume-detail__fix-tag">
<svg viewBox="0 0 16 16" class="resume-detail__fix-tag-icon"><circle cx="8" cy="8" r="7" fill="#FF8D0B"/><text x="8" y="12" text-anchor="middle" font-size="11" fill="#fff" font-weight="bold">!</text></svg>
建议使用AI润色修复
@@ -633,6 +633,7 @@ import {
} from '@/api/resume'
import {ArrowLeft, EditPen, Plus, DeleteFilled, ChatLineRound} from "@element-plus/icons-vue";
import { ElMessage, ElMessageBox } from 'element-plus'
import { EDUCATION_MAP } from '@/stores/index'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
import StepProgressOverlay from '@/components/StepProgressOverlay.vue'