@@ -180,6 +180,17 @@ function initForm() {
aiPolishResult.value = ''
if (props.initialData) {
form.value = { ...props.initialData }
+ // 教育经历:确保 degree 和 studyType 为整数(兼容旧数据可能是中文字符串)
+ if (props.moduleType === 'education') {
+ const degreeMap: Record
= { '大专': 1, '本科': 2, '硕士': 3, '博士': 4 }
+ const studyTypeMap: Record = { '全日制': 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')
diff --git a/src/views/Jobs.vue b/src/views/Jobs.vue
index 110ed89..494ee50 100644
--- a/src/views/Jobs.vue
+++ b/src/views/Jobs.vue
@@ -63,9 +63,9 @@
:displayStyle="selectorDisplayStyle"
@update:industryIds="onIndustryChange"
/>
-
+
+
+
+
{{ filter.selected || filter.label }}
+
+
+
+
+ {{ option.label }}
+
+
+
+
@@ -578,6 +606,7 @@ const filters = ref
([
{ 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([])
+
/** 当前选中的招聘分类 — 直接读 store.jobIntention.recruitCategory */
const selectedEmploymentType = computed(
() => 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
}
diff --git a/src/views/ResumeDetail.vue b/src/views/ResumeDetail.vue
index 648029e..77380ce 100644
--- a/src/views/ResumeDetail.vue
+++ b/src/views/ResumeDetail.vue
@@ -296,7 +296,7 @@
{{ edu.school }}
- {{ edu.major }}{{ edu.degree ? '·' + edu.degree : '' }}
+ {{ edu.major }}{{ edu.degree ? '·' + (EDUCATION_MAP[Number(edu.degree)] || edu.degree) : '' }}
建议使用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'