bug处理
This commit is contained in:
@@ -3,6 +3,16 @@ import aiService from '@/utils/aiRequest'
|
|||||||
import type { ApiResult } from '@/api/auth'
|
import type { ApiResult } from '@/api/auth'
|
||||||
import type { AiResult } from '@/utils/aiRequest'
|
import type { AiResult } from '@/utils/aiRequest'
|
||||||
|
|
||||||
|
// ==================== 岗位总数 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取全网实时岗位总数
|
||||||
|
* GET /job/count
|
||||||
|
*/
|
||||||
|
export function fetchJobCount() {
|
||||||
|
return request.get<any, ApiResult<number>>('/job/count')
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== 匹配详情 ====================
|
// ==================== 匹配详情 ====================
|
||||||
|
|
||||||
/** 匹配度详情 */
|
/** 匹配度详情 */
|
||||||
|
|||||||
@@ -1,5 +1,86 @@
|
|||||||
@use '../variables' as *;
|
@use '../variables' as *;
|
||||||
|
|
||||||
|
/* ==================== 岗位统计卡片 ==================== */
|
||||||
|
.job-stats-cards {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.16rem;
|
||||||
|
margin-left: auto;
|
||||||
|
filter: drop-shadow(0px 2px 10px rgba(0, 221, 179, 0.16));
|
||||||
|
|
||||||
|
/* 全网实时岗位卡片 */
|
||||||
|
&__count-card {
|
||||||
|
position: relative;
|
||||||
|
height: 0.43rem;
|
||||||
|
background: #0A171C;
|
||||||
|
border: 1px solid #1C4C55;
|
||||||
|
border-radius: 0.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 状态指示圆点 */
|
||||||
|
&__dot {
|
||||||
|
//position: absolute;
|
||||||
|
width: 0.08rem;
|
||||||
|
height: 0.08rem;
|
||||||
|
background: #2FE9F4;
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow: 0px 0px 4px rgba(47, 233, 244, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 岗位数字 */
|
||||||
|
&__count-num {
|
||||||
|
|
||||||
|
font-family: 'MiSans';
|
||||||
|
font-weight: 630;
|
||||||
|
font-size: 0.16rem;
|
||||||
|
line-height: 0.21rem;
|
||||||
|
color: #F4FFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 岗位标签文字 */
|
||||||
|
&__count-label {
|
||||||
|
font-family: 'MiSans';
|
||||||
|
font-weight: 330;
|
||||||
|
font-size: 0.10rem;
|
||||||
|
line-height: 0.13rem;
|
||||||
|
color: #8AA8AF;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可投率卡片 */
|
||||||
|
&__match-card {
|
||||||
|
position: relative;
|
||||||
|
width: 0.52rem;
|
||||||
|
height: 0.42rem;
|
||||||
|
background: #08161B;
|
||||||
|
border: 1px solid #20626D;
|
||||||
|
border-radius: 0.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可投率数字 */
|
||||||
|
&__match-num {
|
||||||
|
position: absolute;
|
||||||
|
left: 0.08rem;
|
||||||
|
top: 0.04rem;
|
||||||
|
font-family: 'MiSans';
|
||||||
|
font-weight: 630;
|
||||||
|
font-size: 0.16rem;
|
||||||
|
line-height: 0.21rem;
|
||||||
|
color: #2FE9F4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可投率标签文字 */
|
||||||
|
&__match-label {
|
||||||
|
position: absolute;
|
||||||
|
left: 0.08rem;
|
||||||
|
top: 0.245rem;
|
||||||
|
font-family: 'MiSans';
|
||||||
|
font-weight: 520;
|
||||||
|
font-size: 0.10rem;
|
||||||
|
line-height: 0.13rem;
|
||||||
|
color: #8FB7BE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.job-page-header {
|
.job-page-header {
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,8 @@
|
|||||||
:categoryIds="step2CategoryIds"
|
:categoryIds="step2CategoryIds"
|
||||||
:maxSelect="3"
|
:maxSelect="3"
|
||||||
:level="3"
|
:level="3"
|
||||||
:allowParentSelect="true"
|
:allowParentSelect="false"
|
||||||
|
:allowParentSelectClick="true"
|
||||||
:triggerStyle="prefFilterTriggerStyle"
|
:triggerStyle="prefFilterTriggerStyle"
|
||||||
:displayStyle="prefFilterDisplayStyle"
|
:displayStyle="prefFilterDisplayStyle"
|
||||||
@update:categoryIds="onStep2CategoryChange"
|
@update:categoryIds="onStep2CategoryChange"
|
||||||
@@ -86,7 +87,8 @@
|
|||||||
:industryIds="step2IndustryIds"
|
:industryIds="step2IndustryIds"
|
||||||
:maxSelect="3"
|
:maxSelect="3"
|
||||||
:level="2"
|
:level="2"
|
||||||
:allowParentSelect="true"
|
:allowParentSelect="false"
|
||||||
|
:allowParentSelectClick="true"
|
||||||
:triggerStyle="prefFilterTriggerStyle"
|
:triggerStyle="prefFilterTriggerStyle"
|
||||||
:displayStyle="prefFilterDisplayStyle"
|
:displayStyle="prefFilterDisplayStyle"
|
||||||
@update:industryIds="onStep2IndustryChange"
|
@update:industryIds="onStep2IndustryChange"
|
||||||
|
|||||||
@@ -150,9 +150,9 @@ interface ChatMsg {
|
|||||||
|
|
||||||
// ==================== 本地缓存 ====================
|
// ==================== 本地缓存 ====================
|
||||||
|
|
||||||
/** 获取当前用户的缓存 key */
|
/** 获取当前用户的缓存 key,使用 vuex 中的用户 ID 区分不同账号 */
|
||||||
function getCacheKey(): string {
|
function getCacheKey(): string {
|
||||||
const userId = sessionStorage.getItem('userId') || 'anonymous'
|
const userId = store.state.userInfo?.id || 'anonymous'
|
||||||
return `nova_chat_${userId}`
|
return `nova_chat_${userId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +272,7 @@ async function sendMessage() {
|
|||||||
// ==================== 生命周期 ====================
|
// ==================== 生命周期 ====================
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 从缓存加载聊天记录
|
// 从缓存加载聊天记录(如果用户信息已就绪则按用户隔离)
|
||||||
messages.value = loadChatFromCache()
|
messages.value = loadChatFromCache()
|
||||||
if (messages.value.length > 0) {
|
if (messages.value.length > 0) {
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
@@ -283,6 +283,17 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听 userInfo 变化:用户登录后 userInfo 从 null 变为有值时重新加载对应用户的缓存
|
||||||
|
watch(() => store.state.userInfo, (newInfo, oldInfo) => {
|
||||||
|
if (newInfo && newInfo.id && (!oldInfo || !oldInfo.id)) {
|
||||||
|
// 用户信息就绪,重新加载该用户的聊天记录
|
||||||
|
messages.value = loadChatFromCache()
|
||||||
|
if (messages.value.length > 0) {
|
||||||
|
scrollToBottom()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// 监听 jobId 变化
|
// 监听 jobId 变化
|
||||||
watch(() => props.jobId, (newId, oldId) => {
|
watch(() => props.jobId, (newId, oldId) => {
|
||||||
if (newId && newId !== oldId && !isJobDetailPage.value) {
|
if (newId && newId !== oldId && !isJobDetailPage.value) {
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
:maxSelect="3"
|
:maxSelect="3"
|
||||||
:level="3"
|
:level="3"
|
||||||
:allowParentSelect="false"
|
:allowParentSelect="false"
|
||||||
|
:allowParentSelectClick="true"
|
||||||
:triggerStyle="selectorTriggerStyle"
|
:triggerStyle="selectorTriggerStyle"
|
||||||
:displayStyle="selectorDisplayStyle"
|
:displayStyle="selectorDisplayStyle"
|
||||||
@update:categoryIds="onCategoryChange"
|
@update:categoryIds="onCategoryChange"
|
||||||
@@ -33,6 +34,7 @@
|
|||||||
:maxSelect="3"
|
:maxSelect="3"
|
||||||
:level="2"
|
:level="2"
|
||||||
:allowParentSelect="false"
|
:allowParentSelect="false"
|
||||||
|
:allowParentSelectClick="true"
|
||||||
:triggerStyle="selectorTriggerStyle"
|
:triggerStyle="selectorTriggerStyle"
|
||||||
:displayStyle="selectorDisplayStyle"
|
:displayStyle="selectorDisplayStyle"
|
||||||
@update:industryIds="onIndustryChange"
|
@update:industryIds="onIndustryChange"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-f mb16 prl24 ptb16">
|
<div class="bg-f mb16 prl24 ptb16 dflex aliite-c">
|
||||||
<div class="job-page-header dflex-start aliite-c">
|
<div class="job-page-header dflex-start aliite-c">
|
||||||
<h2 class="job-page-header__title">发现理想职位</h2>
|
<h2 class="job-page-header__title">发现理想职位</h2>
|
||||||
<div class="job-page-header__tabs ml30 ">
|
<div class="job-page-header__tabs ml30 ">
|
||||||
@@ -17,6 +17,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 岗位统计卡片区域 -->
|
||||||
|
<div class="job-stats-cards">
|
||||||
|
<!-- 全网实时岗位卡片 -->
|
||||||
|
<div class="job-stats-cards__count-card prl10 dflex-center aliite-c">
|
||||||
|
<span class="job-stats-cards__dot"></span>
|
||||||
|
<div class="ml8">
|
||||||
|
<div class="job-stats-cards__count-num">{{ formattedJobCount }}</div>
|
||||||
|
<div class="job-stats-cards__count-label">全网实时岗位</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 可投率卡片 -->
|
||||||
|
<div class="job-stats-cards__match-card">
|
||||||
|
<span class="job-stats-cards__match-num">92%</span>
|
||||||
|
<span class="job-stats-cards__match-label">可投率</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -25,7 +41,7 @@
|
|||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import { fetchFavoriteCount, fetchApplyCount } from '@/api/jobs'
|
import { fetchFavoriteCount, fetchApplyCount, fetchJobCount } from '@/api/jobs'
|
||||||
|
|
||||||
// ==================== Props & Emits ====================
|
// ==================== Props & Emits ====================
|
||||||
|
|
||||||
@@ -50,12 +66,32 @@ const isAuthenticated = computed(() => store.state.isAuthenticated)
|
|||||||
|
|
||||||
// ==================== 收藏 & 投递统计 ====================
|
// ==================== 收藏 & 投递统计 ====================
|
||||||
|
|
||||||
|
/** 全网岗位总数 */
|
||||||
|
const jobCount = ref(0)
|
||||||
|
|
||||||
|
/** 格式化岗位总数(每3位加逗号) */
|
||||||
|
const formattedJobCount = computed(() => {
|
||||||
|
return jobCount.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
||||||
|
})
|
||||||
|
|
||||||
/** 收藏总数 */
|
/** 收藏总数 */
|
||||||
const favoriteTotal = ref(0)
|
const favoriteTotal = ref(0)
|
||||||
|
|
||||||
/** 投递总数 */
|
/** 投递总数 */
|
||||||
const applyTotal = ref(0)
|
const applyTotal = ref(0)
|
||||||
|
|
||||||
|
/** 加载岗位总数 */
|
||||||
|
async function loadJobCount() {
|
||||||
|
try {
|
||||||
|
const res = await fetchJobCount()
|
||||||
|
if (res.code === '0' && res.data !== undefined) {
|
||||||
|
jobCount.value = Number(res.data)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('加载岗位总数失败', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 加载收藏统计 */
|
/** 加载收藏统计 */
|
||||||
async function loadFavoriteCount() {
|
async function loadFavoriteCount() {
|
||||||
try {
|
try {
|
||||||
@@ -82,12 +118,20 @@ async function loadApplyCount() {
|
|||||||
|
|
||||||
/** 组件挂载时请求收藏和投递统计 */
|
/** 组件挂载时请求收藏和投递统计 */
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// 岗位总数不需要登录态
|
||||||
|
loadJobCount()
|
||||||
if (isAuthenticated.value) {
|
if (isAuthenticated.value) {
|
||||||
loadFavoriteCount()
|
loadFavoriteCount()
|
||||||
loadApplyCount()
|
loadApplyCount()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 暴露刷新方法给父组件调用,以便收藏/投递操作后同步刷新计数 */
|
||||||
|
defineExpose({
|
||||||
|
loadFavoriteCount,
|
||||||
|
loadApplyCount,
|
||||||
|
})
|
||||||
|
|
||||||
// ==================== 常量数据 ====================
|
// ==================== 常量数据 ====================
|
||||||
|
|
||||||
/** Tab 选项列表,count 用于显示圆形徽章计数 */
|
/** Tab 选项列表,count 用于显示圆形徽章计数 */
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
class="industry-selector__search-item"
|
class="industry-selector__search-item"
|
||||||
v-for="r in searchResults"
|
v-for="r in searchResults"
|
||||||
:key="r.node.id"
|
:key="r.node.id"
|
||||||
@click="toggleItem(r.node)"
|
@click="handleSearchItemClick(r.node)"
|
||||||
>
|
>
|
||||||
<span>{{ r.path }}</span>
|
<span>{{ r.path }}</span>
|
||||||
<!-- 已选中项显示勾选图标 -->
|
<!-- 已选中项显示勾选图标 -->
|
||||||
@@ -69,8 +69,8 @@
|
|||||||
<div class="industry-selector__search-empty">无匹配结果</div>
|
<div class="industry-selector__search-empty">无匹配结果</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 提示:双击选中一级(仅 allowParentSelect 开启时显示) -->
|
<!-- 提示:双击选中一级(仅 allowParentSelect 开启且 allowParentSelectClick 关闭时显示) -->
|
||||||
<div v-if="searchText.length < 2 && allowParentSelect" class="industry-selector__hint">(双击可选中一级行业分类)</div>
|
<div v-if="searchText.length < 2 && allowParentSelect && !allowParentSelectClick" class="industry-selector__hint">(双击可选中一级行业分类)</div>
|
||||||
|
|
||||||
<!-- 分栏联动选择区(搜索关键词不足 2 字符时显示) -->
|
<!-- 分栏联动选择区(搜索关键词不足 2 字符时显示) -->
|
||||||
<div v-if="searchText.length < 2" class="industry-selector__columns">
|
<div v-if="searchText.length < 2" class="industry-selector__columns">
|
||||||
@@ -80,14 +80,14 @@
|
|||||||
class="industry-selector__col-item"
|
class="industry-selector__col-item"
|
||||||
:class="{
|
:class="{
|
||||||
'industry-selector__col-item--active': level > 1 && activeParentId === parent.id,
|
'industry-selector__col-item--active': level > 1 && activeParentId === parent.id,
|
||||||
'industry-selector__col-item--selected': (level === 1 || allowParentSelect) && isSelected(parent.id)
|
'industry-selector__col-item--selected': (level === 1 || allowParentSelect || allowParentSelectClick) && isSelected(parent.id)
|
||||||
}"
|
}"
|
||||||
v-for="parent in industries"
|
v-for="parent in industries"
|
||||||
:key="parent.id"
|
:key="parent.id"
|
||||||
@click="selectParent(parent.id)"
|
@click="selectParent(parent.id)"
|
||||||
>
|
>
|
||||||
{{ parent.name }}
|
{{ parent.name }}
|
||||||
<svg v-if="(level === 1 || allowParentSelect) && isSelected(parent.id)" class="industry-selector__check" viewBox="0 0 12 12">
|
<svg v-if="(level === 1 || allowParentSelect || allowParentSelectClick) && isSelected(parent.id)" class="industry-selector__check" viewBox="0 0 12 12">
|
||||||
<path d="M2 6L5 9L10 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
<path d="M2 6L5 9L10 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,7 +100,7 @@
|
|||||||
:class="{ 'industry-selector__col-item--selected': isSelected(child.id) }"
|
:class="{ 'industry-selector__col-item--selected': isSelected(child.id) }"
|
||||||
v-for="child in activeChildren"
|
v-for="child in activeChildren"
|
||||||
:key="child.id"
|
:key="child.id"
|
||||||
@click="toggleItem({ id: child.id, name: child.name, level: child.level })"
|
@click="selectChild(child)"
|
||||||
>
|
>
|
||||||
{{ child.name }}
|
{{ child.name }}
|
||||||
<svg v-if="isSelected(child.id)" class="industry-selector__check" viewBox="0 0 12 12">
|
<svg v-if="isSelected(child.id)" class="industry-selector__check" viewBox="0 0 12 12">
|
||||||
@@ -144,6 +144,8 @@ const props = withDefaults(
|
|||||||
maxSelect?: number
|
maxSelect?: number
|
||||||
/** 是否允许双击选中一级行业 */
|
/** 是否允许双击选中一级行业 */
|
||||||
allowParentSelect?: boolean
|
allowParentSelect?: boolean
|
||||||
|
/** 是否允许单击选中父级(选子级时自动移除已选的父级) */
|
||||||
|
allowParentSelectClick?: boolean
|
||||||
/** 展示/选择到第几级:1=只选一级,2=选到二级 */
|
/** 展示/选择到第几级:1=只选一级,2=选到二级 */
|
||||||
level?: 1 | 2
|
level?: 1 | 2
|
||||||
/** 父组件传入的触发按钮自定义样式,用于在不同场景下覆盖默认外观 */
|
/** 父组件传入的触发按钮自定义样式,用于在不同场景下覆盖默认外观 */
|
||||||
@@ -156,6 +158,7 @@ const props = withDefaults(
|
|||||||
{
|
{
|
||||||
maxSelect: 3,
|
maxSelect: 3,
|
||||||
allowParentSelect: false,
|
allowParentSelect: false,
|
||||||
|
allowParentSelectClick: false,
|
||||||
level: 2,
|
level: 2,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -212,14 +215,16 @@ const activeChildren = computed<IndustryChild[]>(() => {
|
|||||||
return parent ? parent.children : []
|
return parent ? parent.children : []
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 搜索结果:根据 level 和 allowParentSelect 匹配对应级别 */
|
/** 搜索结果:根据 level 和 allowParentSelect/allowParentSelectClick 匹配对应级别 */
|
||||||
const searchResults = computed(() => {
|
const searchResults = computed(() => {
|
||||||
if (searchText.value.length < 2) return []
|
if (searchText.value.length < 2) return []
|
||||||
const keyword = searchText.value.toLowerCase()
|
const keyword = searchText.value.toLowerCase()
|
||||||
const results: { path: string; node: SelectedNode }[] = []
|
const results: { path: string; node: SelectedNode }[] = []
|
||||||
|
/** 是否允许选中父级 */
|
||||||
|
const canSelectParent = props.allowParentSelect || props.allowParentSelectClick
|
||||||
for (const parent of industries.value) {
|
for (const parent of industries.value) {
|
||||||
// 一级:level=1 时作为末级可搜索,或 allowParentSelect 且 level=2 时可搜索
|
// 一级:level=1 时作为末级可搜索,或允许选中父级且 level=2 时可搜索
|
||||||
if ((props.level === 1 || props.allowParentSelect) && parent.name.toLowerCase().includes(keyword)) {
|
if ((props.level === 1 || canSelectParent) && parent.name.toLowerCase().includes(keyword)) {
|
||||||
results.push({ path: parent.name, node: { id: parent.id, name: parent.name, level: parent.level } })
|
results.push({ path: parent.name, node: { id: parent.id, name: parent.name, level: parent.level } })
|
||||||
}
|
}
|
||||||
if (props.level === 2) {
|
if (props.level === 2) {
|
||||||
@@ -248,6 +253,13 @@ function selectParent(id: string) {
|
|||||||
if (l1) toggleItem({ id: l1.id, name: l1.name, level: l1.level })
|
if (l1) toggleItem({ id: l1.id, name: l1.name, level: l1.level })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// allowParentSelectClick 模式:单击直接选中一级,同时展开子级
|
||||||
|
if (props.allowParentSelectClick) {
|
||||||
|
const l1 = industries.value.find(c => c.id === id)
|
||||||
|
if (l1) toggleItemWithHierarchy({ id: l1.id, name: l1.name, level: l1.level })
|
||||||
|
activeParentId.value = id
|
||||||
|
return
|
||||||
|
}
|
||||||
if (props.allowParentSelect) {
|
if (props.allowParentSelect) {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const lastTime = level1LastClickTime.value[id] || 0
|
const lastTime = level1LastClickTime.value[id] || 0
|
||||||
@@ -279,6 +291,79 @@ function toggleItem(node: SelectedNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* allowParentSelectClick 模式下的选中/取消逻辑:
|
||||||
|
* - 选中子级时,移除已选中的父级
|
||||||
|
* - 选中父级时,移除已选中的该父级下的所有子级
|
||||||
|
*/
|
||||||
|
function toggleItemWithHierarchy(node: SelectedNode) {
|
||||||
|
const idx = selectedItems.value.findIndex(i => i.id === node.id)
|
||||||
|
if (idx >= 0) {
|
||||||
|
// 取消选中
|
||||||
|
selectedItems.value.splice(idx, 1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 获取祖先和后代 ID
|
||||||
|
const ancestorIds = getAncestorIds(node.id)
|
||||||
|
const descendantIds = getDescendantIds(node.id)
|
||||||
|
// 移除已选中的祖先和后代
|
||||||
|
selectedItems.value = selectedItems.value.filter(
|
||||||
|
i => !ancestorIds.has(i.id) && !descendantIds.has(i.id)
|
||||||
|
)
|
||||||
|
// 检查数量限制
|
||||||
|
if (selectedItems.value.length >= props.maxSelect) {
|
||||||
|
ElMessage.warning(`最多只能选择${props.maxSelect}个行业`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selectedItems.value.push({ ...node })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取某个节点的所有祖先 ID(向上查找) */
|
||||||
|
function getAncestorIds(nodeId: string): Set<string> {
|
||||||
|
const ancestors = new Set<string>()
|
||||||
|
for (const parent of industries.value) {
|
||||||
|
for (const child of parent.children) {
|
||||||
|
if (child.id === nodeId) {
|
||||||
|
ancestors.add(parent.id)
|
||||||
|
return ancestors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ancestors
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取某个节点的所有后代 ID(向下查找) */
|
||||||
|
function getDescendantIds(nodeId: string): Set<string> {
|
||||||
|
const descendants = new Set<string>()
|
||||||
|
for (const parent of industries.value) {
|
||||||
|
if (parent.id === nodeId) {
|
||||||
|
for (const child of parent.children) {
|
||||||
|
descendants.add(child.id)
|
||||||
|
}
|
||||||
|
return descendants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return descendants
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 点击右栏二级行业 */
|
||||||
|
function selectChild(child: { id: string; name: string; level: number }) {
|
||||||
|
if (props.allowParentSelectClick) {
|
||||||
|
toggleItemWithHierarchy({ id: child.id, name: child.name, level: child.level })
|
||||||
|
} else {
|
||||||
|
toggleItem({ id: child.id, name: child.name, level: child.level })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索结果项点击处理 */
|
||||||
|
function handleSearchItemClick(node: SelectedNode) {
|
||||||
|
if (props.allowParentSelectClick) {
|
||||||
|
toggleItemWithHierarchy(node)
|
||||||
|
} else {
|
||||||
|
toggleItem(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 从选中区移除指定行业 */
|
/** 从选中区移除指定行业 */
|
||||||
function removeItem(item: SelectedNode) {
|
function removeItem(item: SelectedNode) {
|
||||||
selectedItems.value = selectedItems.value.filter(i => i.id !== item.id)
|
selectedItems.value = selectedItems.value.filter(i => i.id !== item.id)
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
class="job-category-selector__search-item"
|
class="job-category-selector__search-item"
|
||||||
v-for="r in searchResults"
|
v-for="r in searchResults"
|
||||||
:key="r.node.id"
|
:key="r.node.id"
|
||||||
@click="toggleItem(r.node)"
|
@click="handleSearchItemClick(r.node)"
|
||||||
>
|
>
|
||||||
<span>{{ r.path }}</span>
|
<span>{{ r.path }}</span>
|
||||||
<svg v-if="isSelected(r.node.id)" class="job-category-selector__check" viewBox="0 0 12 12">
|
<svg v-if="isSelected(r.node.id)" class="job-category-selector__check" viewBox="0 0 12 12">
|
||||||
@@ -71,8 +71,8 @@
|
|||||||
<div class="job-category-selector__search-empty">无匹配结果</div>
|
<div class="job-category-selector__search-empty">无匹配结果</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 提示:双击选中一二级(仅 allowParentSelect 开启时显示) -->
|
<!-- 提示:双击选中一二级(仅 allowParentSelect 开启且 allowParentSelectClick 关闭时显示) -->
|
||||||
<div v-if="searchText.length < 2 && allowParentSelect" class="job-category-selector__hint">(双击可选中一级/二级岗位分类)</div>
|
<div v-if="searchText.length < 2 && allowParentSelect && !allowParentSelectClick" class="job-category-selector__hint">(双击可选中一级/二级岗位分类)</div>
|
||||||
|
|
||||||
<!-- 三栏联动选择区(搜索关键词不足 2 字符时显示) -->
|
<!-- 三栏联动选择区(搜索关键词不足 2 字符时显示) -->
|
||||||
<div v-if="searchText.length < 2" class="job-category-selector__columns">
|
<div v-if="searchText.length < 2" class="job-category-selector__columns">
|
||||||
@@ -82,14 +82,14 @@
|
|||||||
class="job-category-selector__col-item"
|
class="job-category-selector__col-item"
|
||||||
:class="{
|
:class="{
|
||||||
'job-category-selector__col-item--active': level > 1 && activeLevel1Id === item.id,
|
'job-category-selector__col-item--active': level > 1 && activeLevel1Id === item.id,
|
||||||
'job-category-selector__col-item--selected': (level === 1 || allowParentSelect) && isSelected(item.id)
|
'job-category-selector__col-item--selected': (level === 1 || allowParentSelect || allowParentSelectClick) && isSelected(item.id)
|
||||||
}"
|
}"
|
||||||
v-for="item in categories"
|
v-for="item in categories"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@click="selectLevel1(item.id)"
|
@click="selectLevel1(item.id)"
|
||||||
>
|
>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
<svg v-if="(level === 1 || allowParentSelect) && isSelected(item.id)" class="job-category-selector__check" viewBox="0 0 12 12">
|
<svg v-if="(level === 1 || allowParentSelect || allowParentSelectClick) && isSelected(item.id)" class="job-category-selector__check" viewBox="0 0 12 12">
|
||||||
<path d="M2 6L5 9L10 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
<path d="M2 6L5 9L10 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -101,14 +101,14 @@
|
|||||||
class="job-category-selector__col-item"
|
class="job-category-selector__col-item"
|
||||||
:class="{
|
:class="{
|
||||||
'job-category-selector__col-item--active': level > 2 && activeLevel2Id === item.id,
|
'job-category-selector__col-item--active': level > 2 && activeLevel2Id === item.id,
|
||||||
'job-category-selector__col-item--selected': (level === 2 || allowParentSelect) && isSelected(item.id)
|
'job-category-selector__col-item--selected': (level === 2 || allowParentSelect || allowParentSelectClick) && isSelected(item.id)
|
||||||
}"
|
}"
|
||||||
v-for="item in level2List"
|
v-for="item in level2List"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@click="selectLevel2(item.id)"
|
@click="selectLevel2(item.id)"
|
||||||
>
|
>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
<svg v-if="(level === 2 || allowParentSelect) && isSelected(item.id)" class="job-category-selector__check" viewBox="0 0 12 12">
|
<svg v-if="(level === 2 || allowParentSelect || allowParentSelectClick) && isSelected(item.id)" class="job-category-selector__check" viewBox="0 0 12 12">
|
||||||
<path d="M2 6L5 9L10 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
<path d="M2 6L5 9L10 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
:class="{ 'job-category-selector__col-item--selected': isSelected(item.id) }"
|
:class="{ 'job-category-selector__col-item--selected': isSelected(item.id) }"
|
||||||
v-for="item in level3List"
|
v-for="item in level3List"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@click="toggleItem({ id: item.id, name: item.name, level: item.level })"
|
@click="selectLevel3(item)"
|
||||||
>
|
>
|
||||||
{{ item.name }}
|
{{ item.name }}
|
||||||
<svg v-if="isSelected(item.id)" class="job-category-selector__check" viewBox="0 0 12 12">
|
<svg v-if="isSelected(item.id)" class="job-category-selector__check" viewBox="0 0 12 12">
|
||||||
@@ -167,6 +167,8 @@ const props = withDefaults(
|
|||||||
maxSelect?: number
|
maxSelect?: number
|
||||||
/** 是否允许双击选中一二级分类 */
|
/** 是否允许双击选中一二级分类 */
|
||||||
allowParentSelect?: boolean
|
allowParentSelect?: boolean
|
||||||
|
/** 是否允许单击选中父级(选子级时自动移除已选的父级) */
|
||||||
|
allowParentSelectClick?: boolean
|
||||||
/** 展示/选择到第几级:1=只选一级,2=选到二级,3=选到三级 */
|
/** 展示/选择到第几级:1=只选一级,2=选到二级,3=选到三级 */
|
||||||
level?: 1 | 2 | 3
|
level?: 1 | 2 | 3
|
||||||
/** 父组件传入的触发按钮自定义样式,用于在不同场景下覆盖默认外观 */
|
/** 父组件传入的触发按钮自定义样式,用于在不同场景下覆盖默认外观 */
|
||||||
@@ -179,6 +181,7 @@ const props = withDefaults(
|
|||||||
{
|
{
|
||||||
maxSelect: 3,
|
maxSelect: 3,
|
||||||
allowParentSelect: false,
|
allowParentSelect: false,
|
||||||
|
allowParentSelectClick: false,
|
||||||
level: 3,
|
level: 3,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -248,20 +251,22 @@ const level3List = computed<JobCategoryLeaf[]>(() => {
|
|||||||
return mid ? mid.children : []
|
return mid ? mid.children : []
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 搜索结果:根据 level 和 allowParentSelect 匹配对应级别 */
|
/** 搜索结果:根据 level 和 allowParentSelect/allowParentSelectClick 匹配对应级别 */
|
||||||
const searchResults = computed(() => {
|
const searchResults = computed(() => {
|
||||||
if (searchText.value.length < 2) return []
|
if (searchText.value.length < 2) return []
|
||||||
const keyword = searchText.value.toLowerCase()
|
const keyword = searchText.value.toLowerCase()
|
||||||
const results: { path: string; node: SelectedNode }[] = []
|
const results: { path: string; node: SelectedNode }[] = []
|
||||||
|
/** 是否允许选中父级(双击或单击模式均可) */
|
||||||
|
const canSelectParent = props.allowParentSelect || props.allowParentSelectClick
|
||||||
for (const l1 of categories.value) {
|
for (const l1 of categories.value) {
|
||||||
// 一级:level=1 时作为末级可搜索,或 allowParentSelect 且 level>1 时可搜索
|
// 一级:level=1 时作为末级可搜索,或允许选中父级且 level>1 时可搜索
|
||||||
if ((props.level === 1 || props.allowParentSelect) && l1.name.toLowerCase().includes(keyword)) {
|
if ((props.level === 1 || canSelectParent) && l1.name.toLowerCase().includes(keyword)) {
|
||||||
results.push({ path: l1.name, node: { id: l1.id, name: l1.name, level: l1.level } })
|
results.push({ path: l1.name, node: { id: l1.id, name: l1.name, level: l1.level } })
|
||||||
}
|
}
|
||||||
if (props.level >= 2) {
|
if (props.level >= 2) {
|
||||||
for (const l2 of l1.children) {
|
for (const l2 of l1.children) {
|
||||||
// 二级:level=2 时作为末级可搜索,或 allowParentSelect 且 level=3 时可搜索
|
// 二级:level=2 时作为末级可搜索,或允许选中父级且 level=3 时可搜索
|
||||||
if ((props.level === 2 || props.allowParentSelect) && l2.name.toLowerCase().includes(keyword)) {
|
if ((props.level === 2 || canSelectParent) && l2.name.toLowerCase().includes(keyword)) {
|
||||||
results.push({ path: `${l1.name} → ${l2.name}`, node: { id: l2.id, name: l2.name, level: l2.level } })
|
results.push({ path: `${l1.name} → ${l2.name}`, node: { id: l2.id, name: l2.name, level: l2.level } })
|
||||||
}
|
}
|
||||||
if (props.level === 3) {
|
if (props.level === 3) {
|
||||||
@@ -292,6 +297,14 @@ function selectLevel1(id: string) {
|
|||||||
if (l1) toggleItem({ id: l1.id, name: l1.name, level: l1.level })
|
if (l1) toggleItem({ id: l1.id, name: l1.name, level: l1.level })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// allowParentSelectClick 模式:单击直接选中一级,同时展开子级
|
||||||
|
if (props.allowParentSelectClick) {
|
||||||
|
const l1 = categories.value.find(c => c.id === id)
|
||||||
|
if (l1) toggleItemWithHierarchy({ id: l1.id, name: l1.name, level: l1.level })
|
||||||
|
activeLevel1Id.value = id
|
||||||
|
activeLevel2Id.value = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
if (props.allowParentSelect) {
|
if (props.allowParentSelect) {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const lastTime = level1LastClickTime.value[id] || 0
|
const lastTime = level1LastClickTime.value[id] || 0
|
||||||
@@ -319,6 +332,13 @@ function selectLevel2(id: string) {
|
|||||||
if (l2) toggleItem({ id: l2.id, name: l2.name, level: l2.level })
|
if (l2) toggleItem({ id: l2.id, name: l2.name, level: l2.level })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// allowParentSelectClick 模式:单击直接选中二级,同时展开子级
|
||||||
|
if (props.allowParentSelectClick) {
|
||||||
|
const l2 = level2List.value.find(c => c.id === id)
|
||||||
|
if (l2) toggleItemWithHierarchy({ id: l2.id, name: l2.name, level: l2.level })
|
||||||
|
activeLevel2Id.value = id
|
||||||
|
return
|
||||||
|
}
|
||||||
if (props.allowParentSelect) {
|
if (props.allowParentSelect) {
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const lastTime = level2LastClickTime.value[id] || 0
|
const lastTime = level2LastClickTime.value[id] || 0
|
||||||
@@ -350,6 +370,100 @@ function toggleItem(node: SelectedNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* allowParentSelectClick 模式下的选中/取消逻辑:
|
||||||
|
* - 选中子级时,移除已选中的父级(因为选了更具体的)
|
||||||
|
* - 选中父级时,移除已选中的该父级下的所有子级
|
||||||
|
*/
|
||||||
|
function toggleItemWithHierarchy(node: SelectedNode) {
|
||||||
|
const idx = selectedItems.value.findIndex(i => i.id === node.id)
|
||||||
|
if (idx >= 0) {
|
||||||
|
// 取消选中
|
||||||
|
selectedItems.value.splice(idx, 1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 选中前先做层级互斥处理
|
||||||
|
// 获取当前节点的祖先 ID 列表和后代 ID 列表
|
||||||
|
const ancestorIds = getAncestorIds(node.id)
|
||||||
|
const descendantIds = getDescendantIds(node.id)
|
||||||
|
// 移除已选中的祖先和后代
|
||||||
|
selectedItems.value = selectedItems.value.filter(
|
||||||
|
i => !ancestorIds.has(i.id) && !descendantIds.has(i.id)
|
||||||
|
)
|
||||||
|
// 检查数量限制
|
||||||
|
if (selectedItems.value.length >= props.maxSelect) {
|
||||||
|
ElMessage.warning(`最多只能选择${props.maxSelect}个岗位`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
selectedItems.value.push({ ...node })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取某个节点的所有祖先 ID(向上查找) */
|
||||||
|
function getAncestorIds(nodeId: string): Set<string> {
|
||||||
|
const ancestors = new Set<string>()
|
||||||
|
for (const l1 of categories.value) {
|
||||||
|
for (const l2 of l1.children) {
|
||||||
|
for (const l3 of l2.children) {
|
||||||
|
if (l3.id === nodeId) {
|
||||||
|
ancestors.add(l2.id)
|
||||||
|
ancestors.add(l1.id)
|
||||||
|
return ancestors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (l2.id === nodeId) {
|
||||||
|
ancestors.add(l1.id)
|
||||||
|
return ancestors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ancestors
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 获取某个节点的所有后代 ID(向下查找) */
|
||||||
|
function getDescendantIds(nodeId: string): Set<string> {
|
||||||
|
const descendants = new Set<string>()
|
||||||
|
for (const l1 of categories.value) {
|
||||||
|
if (l1.id === nodeId) {
|
||||||
|
// 是一级,收集所有二级和三级
|
||||||
|
for (const l2 of l1.children) {
|
||||||
|
descendants.add(l2.id)
|
||||||
|
for (const l3 of l2.children) {
|
||||||
|
descendants.add(l3.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return descendants
|
||||||
|
}
|
||||||
|
for (const l2 of l1.children) {
|
||||||
|
if (l2.id === nodeId) {
|
||||||
|
// 是二级,收集所有三级
|
||||||
|
for (const l3 of l2.children) {
|
||||||
|
descendants.add(l3.id)
|
||||||
|
}
|
||||||
|
return descendants
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return descendants
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 点击右栏三级岗位 */
|
||||||
|
function selectLevel3(item: { id: string; name: string; level: number }) {
|
||||||
|
if (props.allowParentSelectClick) {
|
||||||
|
toggleItemWithHierarchy({ id: item.id, name: item.name, level: item.level })
|
||||||
|
} else {
|
||||||
|
toggleItem({ id: item.id, name: item.name, level: item.level })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索结果项点击处理 */
|
||||||
|
function handleSearchItemClick(node: SelectedNode) {
|
||||||
|
if (props.allowParentSelectClick) {
|
||||||
|
toggleItemWithHierarchy(node)
|
||||||
|
} else {
|
||||||
|
toggleItem(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 从选中区移除指定岗位 */
|
/** 从选中区移除指定岗位 */
|
||||||
function removeItem(item: SelectedNode) {
|
function removeItem(item: SelectedNode) {
|
||||||
selectedItems.value = selectedItems.value.filter(i => i.id !== item.id)
|
selectedItems.value = selectedItems.value.filter(i => i.id !== item.id)
|
||||||
|
|||||||
@@ -409,6 +409,7 @@
|
|||||||
:level="2"
|
:level="2"
|
||||||
:maxSelect="3"
|
:maxSelect="3"
|
||||||
:allowParentSelect="false"
|
:allowParentSelect="false"
|
||||||
|
:allowParentSelectClick="true"
|
||||||
:triggerStyle="filterTriggerStyle"
|
:triggerStyle="filterTriggerStyle"
|
||||||
:displayStyle="filterDisplayStyle"
|
:displayStyle="filterDisplayStyle"
|
||||||
@update:industryIds="onHomeIndustryChange"
|
@update:industryIds="onHomeIndustryChange"
|
||||||
@@ -428,6 +429,7 @@
|
|||||||
:level="3"
|
:level="3"
|
||||||
:maxSelect="3"
|
:maxSelect="3"
|
||||||
:allowParentSelect="false"
|
:allowParentSelect="false"
|
||||||
|
:allowParentSelectClick="true"
|
||||||
:triggerStyle="filterTriggerStyle"
|
:triggerStyle="filterTriggerStyle"
|
||||||
:displayStyle="filterDisplayStyle"
|
:displayStyle="filterDisplayStyle"
|
||||||
@update:categoryIds="onHomeCategoryChange"
|
@update:categoryIds="onHomeCategoryChange"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<SideNav />
|
<SideNav />
|
||||||
<div class="job-detail__content">
|
<div class="job-detail__content">
|
||||||
<!-- 页面标题 + Tab 切换 -->
|
<!-- 页面标题 + Tab 切换 -->
|
||||||
<JobPageHeader :activeTab="cachedTab" />
|
<JobPageHeader ref="jobPageHeaderRef" :activeTab="cachedTab" />
|
||||||
<!-- 主内容卡片 -->
|
<!-- 主内容卡片 -->
|
||||||
<div class="job-detail__main-card">
|
<div class="job-detail__main-card">
|
||||||
<!-- 返回按钮 -->
|
<!-- 返回按钮 -->
|
||||||
@@ -443,6 +443,9 @@ function goBack() {
|
|||||||
/** AiChat 组件引用 */
|
/** AiChat 组件引用 */
|
||||||
const aiChatRef = ref<InstanceType<typeof AiChat> | null>(null)
|
const aiChatRef = ref<InstanceType<typeof AiChat> | null>(null)
|
||||||
|
|
||||||
|
/** JobPageHeader 组件引用,用于刷新收藏/投递计数 */
|
||||||
|
const jobPageHeaderRef = ref<InstanceType<typeof JobPageHeader> | null>(null)
|
||||||
|
|
||||||
/** 问 Nova — 触发 AI 聊天,推送提示消息 */
|
/** 问 Nova — 触发 AI 聊天,推送提示消息 */
|
||||||
function handleAskNova() {
|
function handleAskNova() {
|
||||||
aiChatRef.value?.triggerAsk()
|
aiChatRef.value?.triggerAsk()
|
||||||
@@ -556,6 +559,8 @@ async function toggleFavorite() {
|
|||||||
const wasFavorite = job.isFavorite
|
const wasFavorite = job.isFavorite
|
||||||
job.isFavorite = !job.isFavorite
|
job.isFavorite = !job.isFavorite
|
||||||
ElMessage.success(wasFavorite ? '已取消收藏' : '收藏成功')
|
ElMessage.success(wasFavorite ? '已取消收藏' : '收藏成功')
|
||||||
|
// 同步刷新 JobPageHeader 组件内的收藏计数
|
||||||
|
jobPageHeaderRef.value?.loadFavoriteCount()
|
||||||
// 同步收藏状态到 Jobs 页面缓存,返回时无需刷新列表即可看到最新状态
|
// 同步收藏状态到 Jobs 页面缓存,返回时无需刷新列表即可看到最新状态
|
||||||
const cache = store.state.jobListCache
|
const cache = store.state.jobListCache
|
||||||
if (cache) {
|
if (cache) {
|
||||||
|
|||||||
+13
-4
@@ -15,7 +15,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 页面标题 + Tab 切换 -->
|
<!-- 页面标题 + Tab 切换 -->
|
||||||
<JobPageHeader v-model:activeTab="activeTab" />
|
<JobPageHeader ref="jobPageHeaderRef" v-model:activeTab="activeTab" />
|
||||||
|
|
||||||
<!-- 筛选条件 -->
|
<!-- 筛选条件 -->
|
||||||
<div v-if="isRecommendTab" class="jobs-page__filters-bar" :style="showAiRecommendTip?'':'margin-bottom:0.2rem'">
|
<div v-if="isRecommendTab" class="jobs-page__filters-bar" :style="showAiRecommendTip?'':'margin-bottom:0.2rem'">
|
||||||
@@ -42,7 +42,8 @@
|
|||||||
:categoryIds="selectedCategoryIds"
|
:categoryIds="selectedCategoryIds"
|
||||||
:maxSelect="3"
|
:maxSelect="3"
|
||||||
:level="3"
|
:level="3"
|
||||||
:allowParentSelect="true"
|
:allowParentSelect="false"
|
||||||
|
:allowParentSelectClick="true"
|
||||||
:triggerStyle="selectorTriggerStyle"
|
:triggerStyle="selectorTriggerStyle"
|
||||||
:hoverStyle="selectorHoverStyle"
|
:hoverStyle="selectorHoverStyle"
|
||||||
:displayStyle="selectorDisplayStyle"
|
:displayStyle="selectorDisplayStyle"
|
||||||
@@ -55,7 +56,8 @@
|
|||||||
:industryIds="selectedIndustryIds"
|
:industryIds="selectedIndustryIds"
|
||||||
:maxSelect="3"
|
:maxSelect="3"
|
||||||
:level="2"
|
:level="2"
|
||||||
:allowParentSelect="true"
|
:allowParentSelect="false"
|
||||||
|
:allowParentSelectClick="true"
|
||||||
:triggerStyle="selectorTriggerStyle"
|
:triggerStyle="selectorTriggerStyle"
|
||||||
:hoverStyle="selectorHoverStyle"
|
:hoverStyle="selectorHoverStyle"
|
||||||
:displayStyle="selectorDisplayStyle"
|
:displayStyle="selectorDisplayStyle"
|
||||||
@@ -655,7 +657,7 @@ const aiCategoryNames = computed<string>(() => {
|
|||||||
|
|
||||||
/** 是否显示 AI 推荐提示条 */
|
/** 是否显示 AI 推荐提示条 */
|
||||||
const showAiRecommendTip = computed(() => {
|
const showAiRecommendTip = computed(() => {
|
||||||
return isRecommendTab.value && (aiIndustryNames.value !== null || aiCategoryNames.value !== null)
|
return isRecommendTab.value && (aiIndustryNames.value || aiCategoryNames.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
/** 行业选择变更回调 */
|
/** 行业选择变更回调 */
|
||||||
@@ -1459,6 +1461,9 @@ function goToDetail(job: JobItem) {
|
|||||||
const dislikeDialogRef = ref<InstanceType<typeof JobDislikeDialog> | null>(null)
|
const dislikeDialogRef = ref<InstanceType<typeof JobDislikeDialog> | null>(null)
|
||||||
const feedbackDialogRef = ref<InstanceType<typeof JobFeedbackDialog> | null>(null)
|
const feedbackDialogRef = ref<InstanceType<typeof JobFeedbackDialog> | null>(null)
|
||||||
|
|
||||||
|
/** JobPageHeader 组件引用,用于刷新收藏/投递计数 */
|
||||||
|
const jobPageHeaderRef = ref<InstanceType<typeof JobPageHeader> | null>(null)
|
||||||
|
|
||||||
/** 收藏/取消收藏岗位 */
|
/** 收藏/取消收藏岗位 */
|
||||||
async function toggleFavorite(job: JobItem) {
|
async function toggleFavorite(job: JobItem) {
|
||||||
try {
|
try {
|
||||||
@@ -1471,6 +1476,8 @@ async function toggleFavorite(job: JobItem) {
|
|||||||
ElMessage.success(wasFavorite ? '已取消收藏' : '收藏成功')
|
ElMessage.success(wasFavorite ? '已取消收藏' : '收藏成功')
|
||||||
// 刷新收藏统计
|
// 刷新收藏统计
|
||||||
loadFavoriteCount()
|
loadFavoriteCount()
|
||||||
|
// 同步刷新 JobPageHeader 组件内的收藏计数
|
||||||
|
jobPageHeaderRef.value?.loadFavoriteCount()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('收藏操作失败', e)
|
console.error('收藏操作失败', e)
|
||||||
@@ -1486,6 +1493,8 @@ async function removeFavoriteFromList(job: JobItem, index: number) {
|
|||||||
total.value = Math.max(0, total.value - 1)
|
total.value = Math.max(0, total.value - 1)
|
||||||
ElMessage.success('已取消收藏')
|
ElMessage.success('已取消收藏')
|
||||||
loadFavoriteCount()
|
loadFavoriteCount()
|
||||||
|
// 同步刷新 JobPageHeader 组件内的收藏计数
|
||||||
|
jobPageHeaderRef.value?.loadFavoriteCount()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('取消收藏失败', e)
|
console.error('取消收藏失败', e)
|
||||||
|
|||||||
+4
-2
@@ -126,7 +126,7 @@
|
|||||||
<input ref="fileInputRef" type="file" accept=".pdf,.doc,.docx" class="login-view__file-input" @change="handleFileChange" />
|
<input ref="fileInputRef" type="file" accept=".pdf,.doc,.docx" class="login-view__file-input" @change="handleFileChange" />
|
||||||
<!-- 上传按钮 -->
|
<!-- 上传按钮 -->
|
||||||
<button class="login-view__upload-btn" @click="triggerFileInput">+ 上传简历</button>
|
<button class="login-view__upload-btn" @click="triggerFileInput">+ 上传简历</button>
|
||||||
<div class="tac"><span class="color-middle cursor-po fs14" @click="handleEnterDashboard">跳过,稍后填写</span></div>
|
<!-- <div class="tac"><span class="color-middle cursor-po fs14" @click="handleEnterDashboard">跳过,稍后填写</span></div>-->
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 状态2:解析中 parsing -->
|
<!-- 状态2:解析中 parsing -->
|
||||||
@@ -210,7 +210,9 @@
|
|||||||
<template v-if="resumeState === 'failed'">
|
<template v-if="resumeState === 'failed'">
|
||||||
<div class="login-view__heading m0auto">
|
<div class="login-view__heading m0auto">
|
||||||
<h2 class="login-view__title">简历上传失败</h2>
|
<h2 class="login-view__title">简历上传失败</h2>
|
||||||
<p class="login-view__subtitle">文件没有解析成功,请检查格式后重新上传 或 <span class="color-accent cursor-po" @click="handleEnterDashboard">跳过,稍后填写</span></p>
|
<p class="login-view__subtitle">文件没有解析成功,请检查格式后重新上传
|
||||||
|
<!-- 或 <span class="color-accent cursor-po" @click="handleEnterDashboard">跳过,稍后填写</span>-->
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- 失败面板 -->
|
<!-- 失败面板 -->
|
||||||
<div class="login-view__failed-panel">
|
<div class="login-view__failed-panel">
|
||||||
|
|||||||
Reference in New Issue
Block a user