意向字段全部改成使用招聘分类
This commit is contained in:
+4
-7
@@ -75,8 +75,6 @@ export interface JobListParams {
|
|||||||
categoryIds?: number[]
|
categoryIds?: number[]
|
||||||
/** 行业 ID 列表 */
|
/** 行业 ID 列表 */
|
||||||
industryIds?: number[]
|
industryIds?: number[]
|
||||||
/** 工作类型:0=全职 1=兼职 */
|
|
||||||
employmentType?: number
|
|
||||||
/** 指定岗位 ID 列表(用于收藏列表) */
|
/** 指定岗位 ID 列表(用于收藏列表) */
|
||||||
jobIds?: number[]
|
jobIds?: number[]
|
||||||
/** 岗位状态过滤(0=有效 1=已下架 2=已过期,可多选,null或空=查所有) */
|
/** 岗位状态过滤(0=有效 1=已下架 2=已过期,可多选,null或空=查所有) */
|
||||||
@@ -100,8 +98,10 @@ export interface JobIntention {
|
|||||||
regionCodes?: string[]
|
regionCodes?: string[]
|
||||||
/** 期望行业 ID 列表 */
|
/** 期望行业 ID 列表 */
|
||||||
industryIds?: number[]
|
industryIds?: number[]
|
||||||
/** 就业类型:0=全职,1=实习 */
|
/** 就业类型:0=校招,1=实习,2=社招 */
|
||||||
employmentType?: number
|
employmentType?: number | null
|
||||||
|
/** 招聘分类:0=社招,1=校招,2=实习,3=其他 */
|
||||||
|
recruitCategory?: number | null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -128,9 +128,6 @@ export function saveJobIntention(data: JobIntention) {
|
|||||||
* @param params 岗位列表查询参数
|
* @param params 岗位列表查询参数
|
||||||
*/
|
*/
|
||||||
export function fetchJobList(params: JobListParams = {}) {
|
export function fetchJobList(params: JobListParams = {}) {
|
||||||
//兼容旧的数据组件,让招聘分类参数从工作类型那里拿,工作类型不需要传了
|
|
||||||
params.recruitCategory = params.employmentType
|
|
||||||
delete params.employmentType
|
|
||||||
return request.post<any, ApiResult<JobPageData>>('/job/list', {
|
return request.post<any, ApiResult<JobPageData>>('/job/list', {
|
||||||
pageNum: params.pageNum ?? 1,
|
pageNum: params.pageNum ?? 1,
|
||||||
pageSize: params.pageSize ?? 15,
|
pageSize: params.pageSize ?? 15,
|
||||||
|
|||||||
@@ -346,11 +346,21 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.16rem;
|
font-size: 0.16rem;
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
cursor: default;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 0.08rem;
|
gap: 0.08rem;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:disabled):hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 登录中转圈动画 */
|
/* 登录中转圈动画 */
|
||||||
|
|||||||
@@ -109,6 +109,7 @@
|
|||||||
import { ref, computed, onMounted, watch } from 'vue'
|
import { ref, computed, onMounted, watch } from 'vue'
|
||||||
import { fetchJobDetail } from '@/api/jobs'
|
import { fetchJobDetail } from '@/api/jobs'
|
||||||
import type { JobDetailData } from '@/api/jobs'
|
import type { JobDetailData } from '@/api/jobs'
|
||||||
|
import { formatEmploymentType } from '@/stores/index'
|
||||||
|
|
||||||
/** 组件 Props */
|
/** 组件 Props */
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -150,11 +151,7 @@ const formatTime = computed(() => {
|
|||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 工作类型映射 */
|
/** 工作类型映射 — 使用全局统一的 formatEmploymentType */
|
||||||
function formatEmploymentType(type: number | undefined): string {
|
|
||||||
const map: Record<number, string> = { 0: '全职', 1: '兼职' }
|
|
||||||
return map[type ?? -1] ?? ''
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 加载岗位详情 */
|
/** 加载岗位详情 */
|
||||||
async function loadDetail() {
|
async function loadDetail() {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
<span v-for="name in intentionCategoryNames" :key="'cat-' + name" class="agent-setting-panel__goal-tag">{{ name }}</span>
|
<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 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 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>
|
<span v-if="intentionEmploymentLabel" class="agent-setting-panel__goal-tag">{{ intentionEmploymentLabel }}</span>
|
||||||
<!-- 无意向时的空状态 -->
|
<!-- 无意向时的空状态 -->
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -282,8 +282,8 @@ const intentionIndustryNames = computed(() => (store.state.jobIntention?.industr
|
|||||||
/** 求职意向 — 地区名称列表 */
|
/** 求职意向 — 地区名称列表 */
|
||||||
const intentionRegionNames = computed(() => (store.state.jobIntention?.regionCodes || []).map((code: string) => resolveRegionName(code)).filter(Boolean))
|
const intentionRegionNames = computed(() => (store.state.jobIntention?.regionCodes || []).map((code: string) => resolveRegionName(code)).filter(Boolean))
|
||||||
|
|
||||||
/** 求职意向 — 就业类型文案 */
|
/** 求职意向 — 招聘分类文案 */
|
||||||
const intentionEmploymentLabel = computed(() => formatEmploymentType(store.state.jobIntention?.employmentType))
|
const intentionEmploymentLabel = computed(() => formatEmploymentType(store.state.jobIntention?.recruitCategory))
|
||||||
|
|
||||||
// ==================== 浏览器插件 ====================
|
// ==================== 浏览器插件 ====================
|
||||||
|
|
||||||
@@ -336,7 +336,7 @@ function downloadExtension() {
|
|||||||
// ==================== 求职助手配置 ====================
|
// ==================== 求职助手配置 ====================
|
||||||
|
|
||||||
/** 是否为实习类型 */
|
/** 是否为实习类型 */
|
||||||
const isInternship = computed(() => store.state.jobIntention?.employmentType === 1)
|
const isInternship = computed(() => store.state.jobIntention?.recruitCategory === 2)
|
||||||
|
|
||||||
/** 配置表单数据 */
|
/** 配置表单数据 */
|
||||||
const configForm = ref({
|
const configForm = ref({
|
||||||
|
|||||||
@@ -403,7 +403,7 @@ const showJobGoalDialog = ref(false)
|
|||||||
const intentionCategoryNames = computed(() => (store.state.jobIntention.categoryIds || []).map((id: number) => resolveJobCategoryName(id)))
|
const intentionCategoryNames = computed(() => (store.state.jobIntention.categoryIds || []).map((id: number) => resolveJobCategoryName(id)))
|
||||||
const intentionIndustryNames = computed(() => (store.state.jobIntention.industryIds || []).map((id: number) => resolveIndustryName(id)))
|
const intentionIndustryNames = computed(() => (store.state.jobIntention.industryIds || []).map((id: number) => resolveIndustryName(id)))
|
||||||
const intentionRegionNames = computed(() => (store.state.jobIntention.regionCodes || []).map((code: string) => resolveRegionName(code)))
|
const intentionRegionNames = computed(() => (store.state.jobIntention.regionCodes || []).map((code: string) => resolveRegionName(code)))
|
||||||
const intentionEmploymentLabel = computed(() => formatEmploymentType(store.state.jobIntention.employmentType))
|
const intentionEmploymentLabel = computed(() => formatEmploymentType(store.state.jobIntention.recruitCategory))
|
||||||
|
|
||||||
interface MatchedJobItem extends JobListItem { feedback: string }
|
interface MatchedJobItem extends JobListItem { feedback: string }
|
||||||
const matchedJobs = ref<MatchedJobItem[]>([])
|
const matchedJobs = ref<MatchedJobItem[]>([])
|
||||||
@@ -418,7 +418,7 @@ async function loadMatchedJobs() {
|
|||||||
loadingMatchJobs.value = true; matchedJobs.value = []
|
loadingMatchJobs.value = true; matchedJobs.value = []
|
||||||
try {
|
try {
|
||||||
const intention = store.state.jobIntention
|
const intention = store.state.jobIntention
|
||||||
const res = await fetchJobList({ pageNum: 1, pageSize: 30, regionCodes: intention.regionCodes?.length ? intention.regionCodes : undefined, categoryIds: intention.categoryIds?.length ? intention.categoryIds : undefined, industryIds: intention.industryIds?.length ? intention.industryIds : undefined, employmentType: intention.employmentType ?? undefined })
|
const res = await fetchJobList({ pageNum: 1, pageSize: 30, regionCodes: intention.regionCodes?.length ? intention.regionCodes : undefined, categoryIds: intention.categoryIds?.length ? intention.categoryIds : undefined, industryIds: intention.industryIds?.length ? intention.industryIds : undefined, recruitCategory: intention.recruitCategory ?? undefined })
|
||||||
if (res.code === '0' && res.data && res.data.list.length > 0) {
|
if (res.code === '0' && res.data && res.data.list.length > 0) {
|
||||||
const shuffled = [...res.data.list].sort(() => Math.random() - 0.5)
|
const shuffled = [...res.data.list].sort(() => Math.random() - 0.5)
|
||||||
matchedJobs.value = shuffled.slice(0, 3).map(item => ({ ...item, feedback: '' }))
|
matchedJobs.value = shuffled.slice(0, 3).map(item => ({ ...item, feedback: '' }))
|
||||||
@@ -434,7 +434,7 @@ function handleDislike(index: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 第3步:网申常见问题 ====================
|
// ==================== 第3步:网申常见问题 ====================
|
||||||
const isInternship = computed(() => store.state.jobIntention.employmentType === 1)
|
const isInternship = computed(() => store.state.jobIntention.recruitCategory === 2)
|
||||||
const step3Sub = ref(1)
|
const step3Sub = ref(1)
|
||||||
const step3Form = reactive({
|
const step3Form = reactive({
|
||||||
acceptDeptTransfer: '', acceptLocationTransfer: '',
|
acceptDeptTransfer: '', acceptLocationTransfer: '',
|
||||||
@@ -481,7 +481,7 @@ const setupComplete = ref(false)
|
|||||||
function handleStep4Complete() {
|
function handleStep4Complete() {
|
||||||
const step4Data = settingsPanelRef.value?.getData()
|
const step4Data = settingsPanelRef.value?.getData()
|
||||||
const allSettings = {
|
const allSettings = {
|
||||||
jobType: store.state.jobIntention.employmentType === 1 ? 1 : 2,
|
jobType: store.state.jobIntention.recruitCategory === 2 ? 1 : 2,
|
||||||
agentMode: step4Data?.agentMode ?? 1,
|
agentMode: step4Data?.agentMode ?? 1,
|
||||||
weeklyTarget: step4Data?.weeklyTarget ?? 2,
|
weeklyTarget: step4Data?.weeklyTarget ?? 2,
|
||||||
autoOptimizeResume: step4Data?.autoOptimizeResume ?? 1,
|
autoOptimizeResume: step4Data?.autoOptimizeResume ?? 1,
|
||||||
|
|||||||
@@ -62,10 +62,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 工作类型选择模块 -->
|
<!-- 招聘分类选择模块 -->
|
||||||
<div class="job-goal-dialog__section ">
|
<div class="job-goal-dialog__section ">
|
||||||
<div class="job-goal-dialog__label">*工作类型</div>
|
<div class="job-goal-dialog__label">*招聘分类</div>
|
||||||
<!-- 工作类型按钮组 -->
|
<!-- 招聘分类按钮组 -->
|
||||||
<div class="job-goal-dialog__type-group">
|
<div class="job-goal-dialog__type-group">
|
||||||
<button
|
<button
|
||||||
v-for="t in jobTypes"
|
v-for="t in jobTypes"
|
||||||
@@ -118,7 +118,7 @@ const selectedIndustryIds = ref<number[]>([])
|
|||||||
const selectedRegionCodes = ref<string[]>([])
|
const selectedRegionCodes = ref<string[]>([])
|
||||||
const selectedJobType = ref('全职')
|
const selectedJobType = ref('全职')
|
||||||
|
|
||||||
/** 工作类型选项列表 — 从全局常量提取 label 生成 */
|
/** 招聘分类选项列表 — 从全局常量提取 label 生成 */
|
||||||
const jobTypes = JOB_TYPE_OPTIONS.map(item => item.label)
|
const jobTypes = JOB_TYPE_OPTIONS.map(item => item.label)
|
||||||
|
|
||||||
/** 弹窗打开时从 store 同步数据到本地编辑副本 */
|
/** 弹窗打开时从 store 同步数据到本地编辑副本 */
|
||||||
@@ -128,7 +128,7 @@ watch(() => props.modelValue, (v) => {
|
|||||||
selectedCategoryIds.value = [...(intention.categoryIds || [])]
|
selectedCategoryIds.value = [...(intention.categoryIds || [])]
|
||||||
selectedIndustryIds.value = [...(intention.industryIds || [])]
|
selectedIndustryIds.value = [...(intention.industryIds || [])]
|
||||||
selectedRegionCodes.value = [...(intention.regionCodes || [])]
|
selectedRegionCodes.value = [...(intention.regionCodes || [])]
|
||||||
selectedJobType.value = formatEmploymentType(intention.employmentType)
|
selectedJobType.value = formatEmploymentType(intention.recruitCategory)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ async function handleSave() {
|
|||||||
categoryIds: [...selectedCategoryIds.value],
|
categoryIds: [...selectedCategoryIds.value],
|
||||||
industryIds: [...selectedIndustryIds.value],
|
industryIds: [...selectedIndustryIds.value],
|
||||||
regionCodes: [...selectedRegionCodes.value],
|
regionCodes: [...selectedRegionCodes.value],
|
||||||
employmentType: JOB_TYPE_OPTIONS.find(o => o.label === selectedJobType.value)?.value ?? 0,
|
recruitCategory: JOB_TYPE_OPTIONS.find(o => o.label === selectedJobType.value)?.value ?? 0,
|
||||||
})
|
})
|
||||||
visible.value = false
|
visible.value = false
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -130,7 +130,7 @@
|
|||||||
<span class="settings-dialog__reminder-tag" v-for="name in intentionRegionNames" :key="name">{{ name }}</span>
|
<span class="settings-dialog__reminder-tag" v-for="name in intentionRegionNames" :key="name">{{ name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-dialog__reminder-group">
|
<div v-if="intentionEmploymentLabel" class="settings-dialog__reminder-group">
|
||||||
<span class="settings-dialog__reminder-group-label">类型</span>
|
<span class="settings-dialog__reminder-group-label">类型</span>
|
||||||
<div class="settings-dialog__reminder-tags">
|
<div class="settings-dialog__reminder-tags">
|
||||||
<span class="settings-dialog__reminder-tag">{{ intentionEmploymentLabel }}</span>
|
<span class="settings-dialog__reminder-tag">{{ intentionEmploymentLabel }}</span>
|
||||||
@@ -366,9 +366,9 @@ const intentionRegionNames = computed(() => {
|
|||||||
return codes.map((code: string) => resolveRegionName(code))
|
return codes.map((code: string) => resolveRegionName(code))
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 就业类型标签 */
|
/** 招聘分类标签 */
|
||||||
const intentionEmploymentLabel = computed(() => {
|
const intentionEmploymentLabel = computed(() => {
|
||||||
return formatEmploymentType(store.state.jobIntention.employmentType)
|
return formatEmploymentType(store.state.jobIntention.recruitCategory)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 编辑目标岗位 — 打开求职目标弹窗 */
|
/** 编辑目标岗位 — 打开求职目标弹窗 */
|
||||||
|
|||||||
+11
-7
@@ -10,21 +10,22 @@ import type { JobIntention } from '@/api/jobs'
|
|||||||
import { fetchUserInfo } from '@/api/auth'
|
import { fetchUserInfo } from '@/api/auth'
|
||||||
import type { UserInfo } from '@/api/auth'
|
import type { UserInfo } from '@/api/auth'
|
||||||
|
|
||||||
/** 工作类型选项:label → 接口参数 employmentType(0=全职 1=实习) */
|
/** 招聘分类选项:label → 接口参数 recruitCategory */
|
||||||
export const JOB_TYPE_OPTIONS: { label: string; value: number }[] = [
|
export const JOB_TYPE_OPTIONS: { label: string; value: number }[] = [
|
||||||
{ label: '社招', value: 0 },
|
{ label: '社招', value: 0 },
|
||||||
{ label: '校招', value: 1 },
|
{ label: '校招', value: 1 },
|
||||||
{ label: '实习', value: 2 },
|
{ label: '实习', value: 2 },
|
||||||
]
|
]
|
||||||
|
|
||||||
/** 工作类型映射:数字 → 中文标签 */
|
/** 招聘分类映射:数字 → 中文标签 */
|
||||||
export const JOB_TYPE_MAP: Record<number, string> = Object.fromEntries(
|
export const JOB_TYPE_MAP: Record<number, string> = Object.fromEntries(
|
||||||
JOB_TYPE_OPTIONS.map(item => [item.value, item.label]),
|
JOB_TYPE_OPTIONS.map(item => [item.value, item.label]),
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 根据 employmentType 值获取中文标签,未匹配返回"未知" */
|
/** 根据 recruitCategory 值获取中文标签,未匹配返回空字符串 */
|
||||||
export function formatEmploymentType(type: number | undefined | null): string {
|
export function formatEmploymentType(type: number | undefined | null): string {
|
||||||
return JOB_TYPE_MAP[type ?? -1] ?? '未知'
|
if (type === undefined || type === null) return ''
|
||||||
|
return JOB_TYPE_MAP[type] ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 职位列表页缓存数据(从详情页返回时恢复用) */
|
/** 职位列表页缓存数据(从详情页返回时恢复用) */
|
||||||
@@ -135,7 +136,8 @@ export default createStore<RootState>({
|
|||||||
categoryIds: [],
|
categoryIds: [],
|
||||||
regionCodes: [],
|
regionCodes: [],
|
||||||
industryIds: [],
|
industryIds: [],
|
||||||
employmentType: 0,
|
employmentType: null,
|
||||||
|
recruitCategory: null,
|
||||||
},
|
},
|
||||||
userInfo: null,
|
userInfo: null,
|
||||||
showSettings: false,
|
showSettings: false,
|
||||||
@@ -189,7 +191,8 @@ export default createStore<RootState>({
|
|||||||
categoryIds: data.categoryIds ?? [],
|
categoryIds: data.categoryIds ?? [],
|
||||||
regionCodes: data.regionCodes ?? [],
|
regionCodes: data.regionCodes ?? [],
|
||||||
industryIds: data.industryIds ?? [],
|
industryIds: data.industryIds ?? [],
|
||||||
employmentType: data.employmentType ?? 0,
|
employmentType: data.employmentType ?? null,
|
||||||
|
recruitCategory: data.recruitCategory ?? null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
SET_USER_INFO(state, data: UserInfo | null) {
|
SET_USER_INFO(state, data: UserInfo | null) {
|
||||||
@@ -274,7 +277,8 @@ export default createStore<RootState>({
|
|||||||
categoryIds: [],
|
categoryIds: [],
|
||||||
regionCodes: [],
|
regionCodes: [],
|
||||||
industryIds: [],
|
industryIds: [],
|
||||||
employmentType: 0,
|
employmentType: null,
|
||||||
|
recruitCategory: null,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+11
-11
@@ -447,18 +447,18 @@ const filters = ref<FilterItem[]>([
|
|||||||
{ label: '城市', key: 'city', selected: '' },
|
{ label: '城市', key: 'city', selected: '' },
|
||||||
{ label: '岗位', key: 'position', selected: '' },
|
{ label: '岗位', key: 'position', selected: '' },
|
||||||
{ label: '行业', key: 'industry', selected: '' },
|
{ label: '行业', key: 'industry', selected: '' },
|
||||||
{ label: '工作类型', key: 'jobType', selected: '' },
|
{ label: '招聘分类', key: 'jobType', selected: '' },
|
||||||
])
|
])
|
||||||
|
|
||||||
/** 工作类型选项映射:从全局 store 常量统一引入 */
|
/** 招聘分类选项映射:从全局 store 常量统一引入 */
|
||||||
const jobTypeOptions = JOB_TYPE_OPTIONS
|
const jobTypeOptions = JOB_TYPE_OPTIONS
|
||||||
|
|
||||||
/** 工作类型下拉菜单是否显示 */
|
/** 招聘分类下拉菜单是否显示 */
|
||||||
const showJobTypeDropdown = ref(false)
|
const showJobTypeDropdown = ref(false)
|
||||||
|
|
||||||
/** 当前选中的工作类型 — 直接读 store.jobIntention.employmentType */
|
/** 当前选中的招聘分类 — 直接读 store.jobIntention.recruitCategory */
|
||||||
const selectedEmploymentType = computed<number | null>(
|
const selectedEmploymentType = computed<number | null>(
|
||||||
() => store.state.jobIntention.employmentType ?? null,
|
() => store.state.jobIntention.recruitCategory ?? null,
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 选中的行业 id 数组 — 直接读 store.jobIntention.industryIds */
|
/** 选中的行业 id 数组 — 直接读 store.jobIntention.industryIds */
|
||||||
@@ -500,24 +500,24 @@ function onRegionChange(codes: string[]) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 点击筛选条件按钮 — 仅工作类型展开下拉 */
|
/** 点击筛选条件按钮 — 仅招聘分类展开下拉 */
|
||||||
function handleFilterClick(filter: FilterItem) {
|
function handleFilterClick(filter: FilterItem) {
|
||||||
if (filter.key === 'jobType') {
|
if (filter.key === 'jobType') {
|
||||||
showJobTypeDropdown.value = !showJobTypeDropdown.value
|
showJobTypeDropdown.value = !showJobTypeDropdown.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 选中工作类型选项 */
|
/** 选中招聘分类选项 */
|
||||||
function selectJobType(filter: FilterItem, option: { label: string; value: number }) {
|
function selectJobType(filter: FilterItem, option: { label: string; value: number }) {
|
||||||
filter.selected = option.label
|
filter.selected = option.label
|
||||||
showJobTypeDropdown.value = false
|
showJobTypeDropdown.value = false
|
||||||
store.dispatch('saveJobIntention', {
|
store.dispatch('saveJobIntention', {
|
||||||
...store.state.jobIntention,
|
...store.state.jobIntention,
|
||||||
employmentType: option.value,
|
recruitCategory: option.value,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 监听 store 中 employmentType 变化,同步工作类型筛选按钮的显示文字 */
|
/** 监听 store 中 recruitCategory 变化,同步招聘分类筛选按钮的显示文字 */
|
||||||
watch(selectedEmploymentType, (val) => {
|
watch(selectedEmploymentType, (val) => {
|
||||||
const jobTypeFilter = filters.value.find(f => f.key === 'jobType')
|
const jobTypeFilter = filters.value.find(f => f.key === 'jobType')
|
||||||
if (jobTypeFilter && val !== null) {
|
if (jobTypeFilter && val !== null) {
|
||||||
@@ -765,9 +765,9 @@ function buildParams(): JobListParams {
|
|||||||
if (selectedIndustryIds.value.length) {
|
if (selectedIndustryIds.value.length) {
|
||||||
params.industryIds = selectedIndustryIds.value
|
params.industryIds = selectedIndustryIds.value
|
||||||
}
|
}
|
||||||
// 工作类型筛选
|
// 招聘分类筛选
|
||||||
if (selectedEmploymentType.value !== null) {
|
if (selectedEmploymentType.value !== null) {
|
||||||
params.employmentType = selectedEmploymentType.value
|
params.recruitCategory = selectedEmploymentType.value
|
||||||
}
|
}
|
||||||
// 搜索关键词
|
// 搜索关键词
|
||||||
if (keyword.value.trim()) {
|
if (keyword.value.trim()) {
|
||||||
|
|||||||
+29
-47
@@ -109,17 +109,25 @@
|
|||||||
<span v-else-if="idx === otpValue.length && !isLoggingIn" class="login-view__otp-cursor"></span>
|
<span v-else-if="idx === otpValue.length && !isLoggingIn" class="login-view__otp-cursor"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 状态按钮(非触发登录) -->
|
<!-- 状态按钮 — 三种状态:登录中 / 倒计时 / 再次发送 -->
|
||||||
<button class="login-view__status-btn" disabled>
|
<button
|
||||||
|
class="login-view__status-btn"
|
||||||
|
:disabled="isLoggingIn || countdown > 0"
|
||||||
|
@click="handleResendCode"
|
||||||
|
>
|
||||||
<!-- 登录中状态 -->
|
<!-- 登录中状态 -->
|
||||||
<template v-if="isLoggingIn">
|
<template v-if="isLoggingIn">
|
||||||
<span class="login-view__spinner"></span>
|
<span class="login-view__spinner"></span>
|
||||||
<span>登录中</span>
|
<span>登录中</span>
|
||||||
</template>
|
</template>
|
||||||
<!-- 倒计时状态 -->
|
<!-- 倒计时状态 -->
|
||||||
<template v-else>
|
<template v-else-if="countdown > 0">
|
||||||
{{ countdown }} 秒后可继续发送
|
{{ countdown }} 秒后可继续发送
|
||||||
</template>
|
</template>
|
||||||
|
<!-- 可再次发送 -->
|
||||||
|
<template v-else>
|
||||||
|
再次发送验证码
|
||||||
|
</template>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -170,14 +178,6 @@ const countdown = ref(0)
|
|||||||
let countdownTimer: ReturnType<typeof setInterval> | null = null
|
let countdownTimer: ReturnType<typeof setInterval> | null = null
|
||||||
/** 是否正在调用登录接口 */
|
/** 是否正在调用登录接口 */
|
||||||
const isLoggingIn = ref(false)
|
const isLoggingIn = ref(false)
|
||||||
/** 用户最后一次输入验证码的时间戳 */
|
|
||||||
let lastOtpInputTime = 0
|
|
||||||
/** 倒计时结束后等待用户输入冷却的定时器 */
|
|
||||||
let returnWaitTimer: ReturnType<typeof setTimeout> | null = null
|
|
||||||
/** 延长倒计时的定时器(每秒递减) */
|
|
||||||
let extendTimer: ReturnType<typeof setInterval> | null = null
|
|
||||||
/** 标记60秒倒计时已结束,等待返回中 */
|
|
||||||
let countdownFinished = false
|
|
||||||
/** OTP 输入框引用 */
|
/** OTP 输入框引用 */
|
||||||
const otpInputRef = ref<HTMLInputElement | null>(null)
|
const otpInputRef = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
@@ -207,33 +207,6 @@ function startCountdown() {
|
|||||||
if (countdown.value <= 0 && countdownTimer) {
|
if (countdown.value <= 0 && countdownTimer) {
|
||||||
clearInterval(countdownTimer)
|
clearInterval(countdownTimer)
|
||||||
countdownTimer = null
|
countdownTimer = null
|
||||||
// 倒计时结束,进入"即将返回"提示阶段
|
|
||||||
triggerReturn()
|
|
||||||
}
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 倒计时结束后的返回逻辑 — 考虑用户最近操作 */
|
|
||||||
function triggerReturn() {
|
|
||||||
countdownFinished = true
|
|
||||||
scheduleReturn()
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 安排返回:用户停止输入5秒后回步骤一,每次输入都重置5秒 */
|
|
||||||
function scheduleReturn() {
|
|
||||||
// 清除之前所有定时器,重新开始5秒倒计时
|
|
||||||
if (returnWaitTimer) { clearTimeout(returnWaitTimer); returnWaitTimer = null }
|
|
||||||
if (extendTimer) { clearInterval(extendTimer); extendTimer = null }
|
|
||||||
|
|
||||||
// 直接重置为5秒倒计时
|
|
||||||
countdown.value = 5
|
|
||||||
extendTimer = setInterval(() => {
|
|
||||||
countdown.value--
|
|
||||||
if (countdown.value <= 0) {
|
|
||||||
if (extendTimer) { clearInterval(extendTimer); extendTimer = null }
|
|
||||||
countdownFinished = false
|
|
||||||
step.value = 1
|
|
||||||
otpValue.value = ''
|
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
@@ -276,21 +249,30 @@ async function handleSendCode() {
|
|||||||
function handleOtpInput() {
|
function handleOtpInput() {
|
||||||
// 过滤非数字字符
|
// 过滤非数字字符
|
||||||
otpValue.value = otpValue.value.replace(/\D/g, '').slice(0, 6)
|
otpValue.value = otpValue.value.replace(/\D/g, '').slice(0, 6)
|
||||||
// 记录用户最后输入时间
|
|
||||||
lastOtpInputTime = Date.now()
|
|
||||||
// 如果倒计时已结束且用户还在输入(未满6位),重新安排返回计时
|
|
||||||
if (countdownFinished && otpValue.value.length < 6) {
|
|
||||||
scheduleReturn()
|
|
||||||
}
|
|
||||||
// 满6位自动触发登录
|
// 满6位自动触发登录
|
||||||
if (otpValue.value.length === 6) {
|
if (otpValue.value.length === 6) {
|
||||||
// 取消返回流程
|
|
||||||
if (returnWaitTimer) { clearTimeout(returnWaitTimer); returnWaitTimer = null }
|
|
||||||
if (extendTimer) { clearInterval(extendTimer); extendTimer = null }
|
|
||||||
handleLogin()
|
handleLogin()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 步骤二:再次发送验证码 */
|
||||||
|
async function handleResendCode() {
|
||||||
|
if (isLoggingIn.value || countdown.value > 0) return
|
||||||
|
try {
|
||||||
|
const res = await sendSmsCode(phone.value)
|
||||||
|
if (res.code === '0') {
|
||||||
|
ElMessage.success('验证码已发送')
|
||||||
|
otpValue.value = ''
|
||||||
|
startCountdown()
|
||||||
|
nextTick(() => focusOtpInput())
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res.msg || '验证码发送失败')
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// 错误已在 request 拦截器中统一处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 调用登录接口 */
|
/** 调用登录接口 */
|
||||||
async function handleLogin() {
|
async function handleLogin() {
|
||||||
isLoggingIn.value = true
|
isLoggingIn.value = true
|
||||||
|
|||||||
Reference in New Issue
Block a user