导师相关接口换offer派接口,职位列表和职位详情,Nova聊天侧边栏UI重写
This commit is contained in:
+82
-74
@@ -1,40 +1,32 @@
|
||||
<template>
|
||||
<!-- Nova AI 对话面板 -->
|
||||
<div class="ai-chat">
|
||||
<!-- 顶部会员横幅 — 点击打开会员购买弹窗 -->
|
||||
<div class="ai-chat__banner" @click="showMemberDialog = true">
|
||||
<span class="dflex-start aliite-c"> <img src="@/assets/images/bule-flash.png" alt="Offer派" class="w16 h19 mr5" /> <span>升级 Pro 开启求职加速</span></span>
|
||||
<span class="ai-chat__banner-arrow">›</span>
|
||||
</div>
|
||||
|
||||
<!-- AI 头像和名称 -->
|
||||
<!-- 顶部头部区域 -->
|
||||
<div class="ai-chat__header">
|
||||
<div class="ai-chat__avatar">👤</div>
|
||||
<div class="ai-chat__info">
|
||||
<div class="ai-chat__name">Nova</div>
|
||||
<div class="ai-chat__desc">你的AI求职助手</div>
|
||||
<div class="ai-chat__header-left">
|
||||
<!-- Nova 头像 -->
|
||||
<img class="ai-chat__avatar" src="@/assets/images/nova-logo.png" alt="Nova" />
|
||||
<!-- 名称和副标题 -->
|
||||
<div class="ai-chat__info">
|
||||
<div class="ai-chat__name">Nova</div>
|
||||
<div class="ai-chat__desc">你的AI求职助理</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 在线状态标签 -->
|
||||
<div class="ai-chat__online-tag">
|
||||
<span class="ai-chat__online-dot"></span>
|
||||
<span>在线</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 聊天消息区域 -->
|
||||
<div class="ai-chat__messages" ref="messagesRef">
|
||||
<!-- AI 欢迎消息 -->
|
||||
<div class="ai-chat__msg ai-chat__msg--ai">
|
||||
<div class="ai-chat__msg-bubble">
|
||||
<div class="ai-chat__msg-title">欢迎回来,{{nickName}}!</div>
|
||||
<div class="ai-chat__msg-text">很高兴再次见到你,让我们继续您通往理想工作的旅程吧。</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ai-chat__messages scrollbar-thin" ref="messagesRef">
|
||||
|
||||
<!-- 快捷问题(仅岗位详情页显示,3个用户提问靠右) -->
|
||||
<div v-if="userQuestions.length > 0" class="ai-chat__quick-questions">
|
||||
<div
|
||||
v-for="(q, i) in userQuestions"
|
||||
:key="i"
|
||||
class="ai-chat__quick-item ai-chat__quick-item--user"
|
||||
@click="sendQuickQuestion(q)"
|
||||
>
|
||||
{{ q }}
|
||||
</div>
|
||||
<!-- 固定欢迎气泡 -->
|
||||
<div class="ai-chat__welcome-bubble">
|
||||
<div class="ai-chat__welcome-title">欢迎使用 Offer派!👋</div>
|
||||
<div class="ai-chat__welcome-text">从找岗位到投递跟进,我会陪你一步步拿下理想Offer。</div>
|
||||
</div>
|
||||
|
||||
<!-- 动态消息列表 -->
|
||||
@@ -47,8 +39,33 @@
|
||||
<div class="ai-chat__msg-bubble" v-html="formatContent(msg.content)"></div>
|
||||
</div>
|
||||
|
||||
<!-- AI 正在思考中加载指示器 -->
|
||||
<AiThinkingIndicator v-if="aiLoading" text="AI正在思考中" />
|
||||
<!-- AI 等待中 3 点动效 -->
|
||||
<div v-if="aiLoading" class="ai-chat__msg ai-chat__msg--ai">
|
||||
<div class="ai-chat__typing-indicator">
|
||||
<span class="ai-chat__typing-dot"></span>
|
||||
<span class="ai-chat__typing-dot"></span>
|
||||
<span class="ai-chat__typing-dot"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 话题推荐模块(固定在输入框上方) -->
|
||||
<div class="ai-chat__topics">
|
||||
<div class="ai-chat__topics-header">
|
||||
<svg class="ai-chat__topics-icon" width="16" height="16" viewBox="0 0 16 16" fill="none">
|
||||
<path d="M8 1L10 6L15 6.5L11.5 10L12.5 15L8 12.5L3.5 15L4.5 10L1 6.5L6 6L8 1Z" fill="#52CAD1"/>
|
||||
</svg>
|
||||
<span class="ai-chat__topics-title">话题推荐</span>
|
||||
</div>
|
||||
<div
|
||||
v-for="(topic, i) in topicList"
|
||||
:key="i"
|
||||
class="ai-chat__topic-btn"
|
||||
@click="sendQuickQuestion(topic)"
|
||||
>
|
||||
{{ topic }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部输入框 -->
|
||||
@@ -56,17 +73,17 @@
|
||||
<input
|
||||
v-model="inputText"
|
||||
class="ai-chat__input"
|
||||
placeholder="搜索职位、公司或关键词..."
|
||||
placeholder="请输入你想问的问题…"
|
||||
@keyup.enter="sendMessage"
|
||||
:disabled="aiLoading"
|
||||
/>
|
||||
<button class="ai-chat__send-btn" @click="sendMessage" :disabled="aiLoading">
|
||||
<span>➤</span>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M4 10L16 4L13 10L16 16L4 10Z" fill="#FFFFFF"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 会员购买弹窗 -->
|
||||
<MemberDialog v-model="showMemberDialog" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -75,8 +92,6 @@ import { ref, computed, watch, nextTick, onMounted } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useStore } from 'vuex'
|
||||
import markdownit from 'markdown-it'
|
||||
import MemberDialog from '@/components/MemberDialog.vue'
|
||||
import AiThinkingIndicator from '@/components/tools/AiThinkingIndicator.vue'
|
||||
import { sendNovaChat } from '@/utils/aiRequest'
|
||||
import type { NovaChatHistoryItem } from '@/utils/aiRequest'
|
||||
import { fetchResumeList } from '@/api/resume'
|
||||
@@ -85,9 +100,9 @@ import { fetchResumeList } from '@/api/resume'
|
||||
|
||||
/** markdown-it 实例,用于将 AI 回复的 Markdown 文本渲染为 HTML */
|
||||
const md = markdownit({
|
||||
html: false, // 禁止原始 HTML(防 XSS)
|
||||
breaks: true, // 将换行符转为 <br>
|
||||
linkify: true, // 自动识别链接
|
||||
html: false,
|
||||
breaks: true,
|
||||
linkify: true,
|
||||
})
|
||||
|
||||
// ==================== Props ====================
|
||||
@@ -98,23 +113,16 @@ const props = defineProps<{
|
||||
jobId?: string
|
||||
}>()
|
||||
|
||||
/** 当前路由(用于判断是否在岗位详情页) */
|
||||
/** 当前路由 */
|
||||
const currentRoute = useRoute()
|
||||
const store = useStore()
|
||||
|
||||
|
||||
/** 用户昵称 — 从全局 store 读取 */
|
||||
const nickName = computed(() => store.state.userInfo?.nick || '')
|
||||
|
||||
// ==================== 状态 ====================
|
||||
|
||||
/** 会员购买弹窗的显示状态 */
|
||||
const showMemberDialog = ref(false)
|
||||
|
||||
/** 输入框内容 */
|
||||
const inputText = ref('')
|
||||
|
||||
/** 消息列表容器 DOM 引用(用于滚动控制) */
|
||||
/** 消息列表容器 DOM 引用 */
|
||||
const messagesRef = ref<HTMLElement>()
|
||||
|
||||
/** AI 是否正在请求中 */
|
||||
@@ -123,13 +131,20 @@ const aiLoading = ref(false)
|
||||
/** 默认简历 ID */
|
||||
const defaultResumeId = ref<string>('')
|
||||
|
||||
// ==================== 话题推荐列表 ====================
|
||||
|
||||
/** 固定话题推荐(始终显示) */
|
||||
const topicList = [
|
||||
'我适合投哪些岗位?',
|
||||
'帮我看看我的简历可以怎么优化?',
|
||||
'近期有哪些岗位值得投?',
|
||||
]
|
||||
|
||||
// ==================== 类型定义 ====================
|
||||
|
||||
/** 聊天消息类型 */
|
||||
interface ChatMsg {
|
||||
/** 角色:assistant-AI助手 user-用户 */
|
||||
role: 'assistant' | 'user'
|
||||
/** 消息内容 */
|
||||
content: string
|
||||
}
|
||||
|
||||
@@ -137,8 +152,6 @@ interface ChatMsg {
|
||||
|
||||
/** 获取当前用户的缓存 key */
|
||||
function getCacheKey(): string {
|
||||
// 用 sessionStorage 中的登录状态 + 一个简单标识区分用户
|
||||
// 如果有更精确的用户 ID 可以替换
|
||||
const userId = sessionStorage.getItem('userId') || 'anonymous'
|
||||
return `nova_chat_${userId}`
|
||||
}
|
||||
@@ -175,19 +188,6 @@ const messages = ref<ChatMsg[]>([])
|
||||
/** 是否在岗位详情页 */
|
||||
const isJobDetailPage = computed(() => currentRoute.name === 'JobDetail' || currentRoute.path.startsWith('/jobs/'))
|
||||
|
||||
/** 快捷问题列表(仅岗位详情页显示的 3 个用户提问) */
|
||||
const quickQuestions = computed(() => {
|
||||
if (!isJobDetailPage.value) return []
|
||||
return [
|
||||
'告诉我这个工作为什么适合我?',
|
||||
'我想修改这个岗位,怎么优化简历?',
|
||||
'帮我针对这个岗位生成一份面试攻略',
|
||||
]
|
||||
})
|
||||
|
||||
/** 用户提问列表(等同于 quickQuestions) */
|
||||
const userQuestions = computed(() => quickQuestions.value)
|
||||
|
||||
// ==================== 内容格式化 ====================
|
||||
|
||||
/** 将消息内容通过 markdown-it 渲染为 HTML */
|
||||
@@ -208,7 +208,7 @@ function scrollToBottom() {
|
||||
|
||||
// ==================== 加载默认简历 ID ====================
|
||||
|
||||
/** 加载默认简历 ID(用于 Nova 对话接口) */
|
||||
/** 加载默认简历 ID */
|
||||
async function loadDefaultResumeId() {
|
||||
try {
|
||||
const res = await fetchResumeList()
|
||||
@@ -223,13 +223,13 @@ async function loadDefaultResumeId() {
|
||||
|
||||
// ==================== 事件处理 ====================
|
||||
|
||||
/** 点击快捷问题,填入输入框并发送 */
|
||||
/** 点击话题推荐,填入输入框并发送 */
|
||||
function sendQuickQuestion(question: string) {
|
||||
inputText.value = question
|
||||
sendMessage()
|
||||
}
|
||||
|
||||
/** 发送消息(回车或点击发送按钮触发) */
|
||||
/** 发送消息 */
|
||||
async function sendMessage() {
|
||||
if (!inputText.value.trim() || aiLoading.value) return
|
||||
|
||||
@@ -256,8 +256,8 @@ async function sendMessage() {
|
||||
jobId: props.jobId || undefined,
|
||||
history,
|
||||
})
|
||||
// 添加 AI 回复消息
|
||||
const aiReply = res?.data?.message || res?.message || '抱歉,我暂时无法回答这个问题。'
|
||||
// 从 res.data.message 取 AI 回复(接口返回结构:{ code, msg, data: { message } })
|
||||
const aiReply = res?.data?.message || '抱歉,我暂时无法回答这个问题。'
|
||||
messages.value.push({ role: 'assistant', content: aiReply })
|
||||
} catch (e) {
|
||||
console.error('Nova 对话请求失败', e)
|
||||
@@ -269,8 +269,6 @@ async function sendMessage() {
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 动态宽度:小屏缩放时逐渐缩小 ====================
|
||||
|
||||
// ==================== 生命周期 ====================
|
||||
|
||||
onMounted(() => {
|
||||
@@ -285,13 +283,23 @@ onMounted(() => {
|
||||
}
|
||||
})
|
||||
|
||||
// 监听 jobId 变化(Jobs 页面点击"问助手"按钮时触发),自动插入 AI 提问消息
|
||||
// 监听 jobId 变化
|
||||
watch(() => props.jobId, (newId, oldId) => {
|
||||
if (newId && newId !== oldId && !isJobDetailPage.value) {
|
||||
// Jobs 页面:以 AI 身份插入提问到对话记录
|
||||
messages.value.push({ role: 'assistant', content: '你想知道关于这个岗位的什么信息?' })
|
||||
saveChatToCache()
|
||||
scrollToBottom()
|
||||
}
|
||||
})
|
||||
|
||||
// ==================== 对外暴露方法 ====================
|
||||
|
||||
/** 触发"问Nova"交互:推送提示消息并聚焦输入框 */
|
||||
function triggerAsk() {
|
||||
messages.value.push({ role: 'assistant', content: '你想知道关于这个岗位的什么信息?' })
|
||||
saveChatToCache()
|
||||
scrollToBottom()
|
||||
}
|
||||
|
||||
defineExpose({ triggerAsk })
|
||||
</script>
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="job-page-header">
|
||||
<div class="bg-f mb16 prl24 ptb16">
|
||||
<div class="job-page-header dflex-start aliite-c">
|
||||
<h2 class="job-page-header__title">发现理想职位</h2>
|
||||
<p class="job-page-header__subtitle">找到最适合你的工作机会</p>
|
||||
</div>
|
||||
<div class="job-page-header__tabs mt20">
|
||||
<div
|
||||
v-for="(tab,index) in tabs"
|
||||
:key="tab.key"
|
||||
class="job-page-header__tab"
|
||||
:class="{ 'job-page-header__tab--active': activeTab === tab.key }"
|
||||
:style="index==0?'padding:0.06rem 0.30rem;':''"
|
||||
@click="handleTabClick(tab.key)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
<div class="job-page-header__tabs ml30 ">
|
||||
<div
|
||||
v-for="(tab,index) in tabs"
|
||||
:key="tab.key"
|
||||
class="job-page-header__tab"
|
||||
:class="{ 'job-page-header__tab--active': activeTab === tab.key }"
|
||||
:style="index==0?'padding:0.06rem 0.20rem;':''"
|
||||
@click="handleTabClick(tab.key)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
<!-- 收藏/已投递 计数圆形徽章 -->
|
||||
<span v-if="tab.count !== undefined" class="job-page-header__tab-badge">{{ tab.count }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -46,11 +48,11 @@ const route = useRoute()
|
||||
|
||||
// ==================== 常量数据 ====================
|
||||
|
||||
/** Tab 选项列表 */
|
||||
/** Tab 选项列表,count 用于显示圆形徽章计数 */
|
||||
const tabs = computed(() => [
|
||||
{ key: 'recommend', label: '推荐' },
|
||||
{ key: 'collected', label: `收藏(${props.favoriteCount ?? 0})` },
|
||||
{ key: 'applied', label: `投递(${props.applyCount ?? 0})` },
|
||||
{ key: 'recommend', label: '推荐', count: undefined },
|
||||
{ key: 'collected', label: '收藏', count: props.favoriteCount ?? 0 },
|
||||
{ key: 'applied', label: '已投递', count: props.applyCount ?? 0 },
|
||||
])
|
||||
|
||||
// ==================== 事件处理 ====================
|
||||
|
||||
@@ -1,49 +1,10 @@
|
||||
<template>
|
||||
<!-- 岗位专属简历定制弹窗:步骤1居中弹窗,步骤2+右侧抽屉 -->
|
||||
<div v-if="modelValue" class="job-resume-custom-dialog" :class="{ 'job-resume-custom-dialog--drawer': currentStep >= 2 }">
|
||||
<!-- 岗位专属简历定制弹窗:直接显示右侧抽屉 -->
|
||||
<div v-if="modelValue" class="job-resume-custom-dialog job-resume-custom-dialog--drawer">
|
||||
<div class="job-resume-custom-dialog__overlay" @click="handleClose"></div>
|
||||
<!-- ===== 步骤一:居中弹窗 ===== -->
|
||||
<div v-if="currentStep === 1" class="job-resume-custom-dialog__panel">
|
||||
<div class="job-resume-custom-dialog__header">
|
||||
<h2 class="job-resume-custom-dialog__title">10s快速定制岗位专属简历</h2>
|
||||
<button class="job-resume-custom-dialog__close-btn" @click="handleClose" aria-label="关闭">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="job-resume-custom-dialog__close-icon"><path d="M12 4L4 12M4 4l8 8" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="isLowMatch" class="job-resume-custom-dialog__tip-bar"><span>你与该岗位的匹配度较低,简历可能无法通过机筛。</span></div>
|
||||
<div class="job-resume-custom-dialog__job-card">
|
||||
<div class="job-resume-custom-dialog__job-left">
|
||||
<div class="job-resume-custom-dialog__company-icon">
|
||||
<img v-if="jobInfo.companyLogoUrl" :src="jobInfo.companyLogoUrl" :alt="jobInfo.company" class="job-resume-custom-dialog__company-logo-img" />
|
||||
<svg v-else viewBox="0 0 24 24" fill="none" class="job-resume-custom-dialog__company-svg"><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"/><path d="M3 13h18" stroke="currentColor" stroke-width="1.5"/></svg>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__job-info">
|
||||
<span class="job-resume-custom-dialog__job-title">{{ jobInfo.title }}</span>
|
||||
<span class="job-resume-custom-dialog__job-sub">{{ jobInfo.location }} · {{ jobInfo.company }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__match-area">
|
||||
<div class="job-resume-custom-dialog__match-ring">
|
||||
<svg viewBox="0 0 60 60" class="job-resume-custom-dialog__ring-svg"><circle cx="30" cy="30" r="24" stroke-width="4" stroke="#E8E8E8" fill="none" opacity="0.3"/><circle cx="30" cy="30" r="24" stroke-width="4" fill="none" stroke="#4FC2C9" stroke-linecap="round" :stroke-dasharray="2*Math.PI*24" :stroke-dashoffset="2*Math.PI*24*(1-jobInfo.matchScore/10)" transform="rotate(-90 30 30)"/></svg>
|
||||
<span class="job-resume-custom-dialog__match-score">{{ jobInfo.matchScore }}</span>
|
||||
</div>
|
||||
<span class="job-resume-custom-dialog__match-label">{{ matchLevelText }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__skills-section">
|
||||
<p class="job-resume-custom-dialog__skills-title">缺少{{ missingSkills.length }}项技能</p>
|
||||
<div class="job-resume-custom-dialog__skills-list">
|
||||
<span v-for="skill in missingSkills" :key="skill" class="job-resume-custom-dialog__skill-tag">{{ skill }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__footer">
|
||||
<button class="job-resume-custom-dialog__primary-btn" @click="goToStep(2)">立即定制简历</button>
|
||||
<span class="job-resume-custom-dialog__skip-link" @click="handleSkip">不优化,直接投递</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ===== 步骤2+:右侧抽屉 ===== -->
|
||||
<div v-if="currentStep >= 2" class="job-resume-custom-dialog__drawer">
|
||||
<!-- ===== 右侧抽屉 ===== -->
|
||||
<div class="job-resume-custom-dialog__drawer">
|
||||
<!-- 抽屉头部 -->
|
||||
<div class="job-resume-custom-dialog__drawer-header">
|
||||
<button class="job-resume-custom-dialog__close-btn" @click="handleClose" aria-label="关闭">
|
||||
@@ -62,7 +23,7 @@
|
||||
<span class="job-resume-custom-dialog__step-num">2</span><span>定制简历</span>
|
||||
</div>
|
||||
<div class="job-resume-custom-dialog__step" :class="{ 'job-resume-custom-dialog__step--active': currentStep === 4 }">
|
||||
<span class="job-resume-custom-dialog__step-num">3</span><span>预览</span>
|
||||
<span class="job-resume-custom-dialog__step-num">3</span><span>简历预览</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 抽屉内容区(可滚动) -->
|
||||
@@ -423,8 +384,8 @@ const fullLoading = ref(false)
|
||||
/** 全屏加载文案 */
|
||||
const fullLoadingText = ref('')
|
||||
|
||||
/** 当前步骤:1-确认入口(居中弹窗) 2-差距分析(右侧抽屉) 3-定制简历 4-预览 */
|
||||
const currentStep = ref(1)
|
||||
/** 当前步骤:2-差距分析(右侧抽屉) 3-定制简历 4-预览 */
|
||||
const currentStep = ref(2)
|
||||
|
||||
/** 缺少的技能列表 */
|
||||
const missingSkills = computed(() => props.jobInfo.missingSkills || [])
|
||||
@@ -602,7 +563,7 @@ function onEditPanelUpdate(data: CustomizeResumeData) {
|
||||
|
||||
/** 关闭弹窗并重置步骤 */
|
||||
function handleClose() {
|
||||
currentStep.value = 1
|
||||
currentStep.value = 2
|
||||
showResumeDropdown.value = false
|
||||
showDownloadMenu.value = false
|
||||
// 重置AI对话和差异对比状态
|
||||
@@ -613,11 +574,7 @@ function handleClose() {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
|
||||
/** 不优化,直接投递 */
|
||||
function handleSkip() {
|
||||
handleClose()
|
||||
emit('skip')
|
||||
}
|
||||
|
||||
|
||||
// ==================== 定制简历选项(步骤三) ====================
|
||||
|
||||
|
||||
@@ -41,7 +41,13 @@
|
||||
</span>
|
||||
<!-- 作品集链接显示在个人信息卡片内 -->
|
||||
<span v-if="profile.portfolioUrl" class="profile-page-content__info-contacts">
|
||||
<span class="profile-page-content__contact-item">🔗 <a :href="profile.portfolioUrl" target="_blank" rel="noopener noreferrer" class="profile-page-content__portfolio-link">{{ profile.portfolioUrl }}</a></span>
|
||||
<span class="profile-page-content__contact-item dib">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="resume-detail__contact-icon mr6" width="0.15rem" height="0.15rem" viewBox="0 0 16 16">
|
||||
<path d="M0 0h16v16H0z" fill="none" />
|
||||
<path fill="none" stroke="currentColor" d="M14.5 8A6.5 6.5 0 0 1 8 14.5M14.5 8A6.5 6.5 0 0 0 8 1.5M14.5 8c0 1.657-2.91 3-6.5 3S1.5 9.657 1.5 8m13 0c0-1.657-2.91-3-6.5-3S1.5 6.343 1.5 8M8 14.5A6.5 6.5 0 0 1 1.5 8M8 14.5c1.657 0 3-2.91 3-6.5S9.657 1.5 8 1.5m0 13c-1.657 0-3-2.91-3-6.5s1.343-6.5 3-6.5M1.5 8A6.5 6.5 0 0 1 8 1.5" />
|
||||
</svg>
|
||||
<a :href="profile.portfolioUrl?.startsWith('http') ? profile.portfolioUrl : `http://${profile.portfolioUrl}`" target="_blank" rel="noopener noreferrer" class="profile-page-content__portfolio-link">{{ profile.portfolioUrl }}</a>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
<div class="industry-selector" ref="selectorRef">
|
||||
|
||||
<!-- 触发按钮:显示已选行业名称或默认文字"行业" -->
|
||||
<div class="industry-selector__trigger" :style="triggerStyle" @click="toggleDropdown">
|
||||
<div class="industry-selector__trigger" :style="{ ...triggerStyle, ...(isHovered || visible ? hoverStyle : {}) }" @click="toggleDropdown" @mouseenter="isHovered = true" @mouseleave="isHovered = false">
|
||||
<span class="industry-selector__display" :style="displayStyle" :title="displayText">{{ displayText }}</span>
|
||||
<svg
|
||||
class="industry-selector__arrow"
|
||||
:class="{ 'industry-selector__arrow--open': visible }"
|
||||
:style="(isHovered || visible) && hoverStyle?.color ? { color: hoverStyle.color } : {}"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
>
|
||||
@@ -147,6 +148,8 @@ const props = withDefaults(
|
||||
level?: 1 | 2
|
||||
/** 父组件传入的触发按钮自定义样式,用于在不同场景下覆盖默认外观 */
|
||||
triggerStyle?: Record<string, string>
|
||||
/** 父组件传入的触发按钮 hover 时的自定义样式 */
|
||||
hoverStyle?: Record<string, string>
|
||||
/** 父组件传入的显示文字自定义样式,用于覆盖 max-width 等默认样式 */
|
||||
displayStyle?: Record<string, string>
|
||||
}>(),
|
||||
@@ -170,6 +173,9 @@ const selectorRef = ref<HTMLElement | null>(null)
|
||||
/** 下拉面板是否可见 */
|
||||
const visible = ref(false)
|
||||
|
||||
/** 触发按钮是否处于 hover 状态 */
|
||||
const isHovered = ref(false)
|
||||
|
||||
/** 搜索关键词 */
|
||||
const searchText = ref('')
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
<div class="job-category-selector" ref="selectorRef">
|
||||
|
||||
<!-- 触发按钮:显示已选岗位名称或默认文字"岗位" -->
|
||||
<div class="job-category-selector__trigger" :style="triggerStyle" @click="toggleDropdown">
|
||||
<div class="job-category-selector__trigger" :style="{ ...triggerStyle, ...(isHovered || visible ? hoverStyle : {}) }" @click="toggleDropdown" @mouseenter="isHovered = true" @mouseleave="isHovered = false">
|
||||
<span class="job-category-selector__display" :style="displayStyle" :title="displayText">{{ displayText }}</span>
|
||||
<svg
|
||||
class="job-category-selector__arrow"
|
||||
:class="{ 'job-category-selector__arrow--open': visible }"
|
||||
:style="(isHovered || visible) && hoverStyle?.color ? { color: hoverStyle.color } : {}"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
>
|
||||
@@ -170,6 +171,8 @@ const props = withDefaults(
|
||||
level?: 1 | 2 | 3
|
||||
/** 父组件传入的触发按钮自定义样式,用于在不同场景下覆盖默认外观 */
|
||||
triggerStyle?: Record<string, string>
|
||||
/** 父组件传入的触发按钮 hover 时的自定义样式 */
|
||||
hoverStyle?: Record<string, string>
|
||||
/** 父组件传入的显示文字自定义样式,用于覆盖 max-width 等默认样式 */
|
||||
displayStyle?: Record<string, string>
|
||||
}>(),
|
||||
@@ -193,6 +196,9 @@ const selectorRef = ref<HTMLElement | null>(null)
|
||||
/** 下拉面板是否可见 */
|
||||
const visible = ref(false)
|
||||
|
||||
/** 触发按钮是否处于 hover 状态 */
|
||||
const isHovered = ref(false)
|
||||
|
||||
/** 搜索关键词 */
|
||||
const searchText = ref('')
|
||||
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
<div class="region-selector" ref="selectorRef">
|
||||
|
||||
<!-- 触发按钮:显示已选地区名称或默认文字"城市" -->
|
||||
<div class="region-selector__trigger" :style="triggerStyle" @click="toggleDropdown">
|
||||
<div class="region-selector__trigger" :style="{ ...triggerStyle, ...(isHovered || visible ? hoverStyle : {}) }" @click="toggleDropdown" @mouseenter="isHovered = true" @mouseleave="isHovered = false">
|
||||
<span class="region-selector__display" :style="displayStyle" :title="displayText">{{ displayText }}</span>
|
||||
<svg
|
||||
class="region-selector__arrow"
|
||||
:class="{ 'region-selector__arrow--open': visible }"
|
||||
:style="(isHovered || visible) && hoverStyle?.color ? { color: hoverStyle.color } : {}"
|
||||
viewBox="0 0 12 12"
|
||||
fill="none"
|
||||
>
|
||||
@@ -157,6 +158,8 @@ const props = withDefaults(
|
||||
maxSelect?: number
|
||||
/** 父组件传入的触发按钮自定义样式,用于在不同场景下覆盖默认外观 */
|
||||
triggerStyle?: Record<string, string>
|
||||
/** 父组件传入的触发按钮 hover 时的自定义样式 */
|
||||
hoverStyle?: Record<string, string>
|
||||
/** 父组件传入的显示文字自定义样式,用于覆盖 max-width 等默认样式 */
|
||||
displayStyle?: Record<string, string>
|
||||
}>(),
|
||||
@@ -179,6 +182,9 @@ const selectorRef = ref<HTMLElement | null>(null)
|
||||
/** 下拉面板是否可见 */
|
||||
const visible = ref(false)
|
||||
|
||||
/** 触发按钮是否处于 hover 状态 */
|
||||
const isHovered = ref(false)
|
||||
|
||||
/** 搜索关键词 */
|
||||
const searchText = ref('')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user