diff --git a/components.d.ts b/components.d.ts index e2cbd87..3645be0 100644 --- a/components.d.ts +++ b/components.d.ts @@ -48,6 +48,7 @@ declare module 'vue' { JobResumeCustomDialog: typeof import('./src/components/JobResumeCustomDialog.vue')['default'] JobResumeCustomEditPanel: typeof import('./src/components/JobResumeCustomEditPanel.vue')['default'] JobResumeTemplate: typeof import('./src/components/JobResumeTemplate.vue')['default'] + JobStatsOverview: typeof import('./src/components/JobStatsOverview.vue')['default'] LoginDialog: typeof import('./src/components/LoginDialog.vue')['default'] MemberAccessDialog: typeof import('./src/components/MemberAccessDialog.vue')['default'] MemberDialog: typeof import('./src/components/MemberDialog.vue')['default'] diff --git a/src/App.vue b/src/App.vue index 090ef25..6fb1174 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,12 +1,15 @@ \ No newline at end of file + diff --git a/src/api/announcement.ts b/src/api/announcement.ts new file mode 100644 index 0000000..a0bb579 --- /dev/null +++ b/src/api/announcement.ts @@ -0,0 +1,178 @@ +import request from '@/utils/request' +import type { ApiResult } from '@/api/auth' + +// ==================== 校招公告列表接口 ==================== + +/** Instant 时间结构(Java Instant 序列化格式) */ +export interface InstantTime { + /** 从 1970-01-01T00:00:00Z 起的秒数 */ + seconds: number + /** 纳秒偏移(0 ~ 999999999) */ + nanos?: number +} + +/** 校招公告列表请求参数 */ +export interface AnnouncementListParams { + /** 当前页码,从1开始 */ + pageNum?: number + /** 每页条数 */ + pageSize?: number + /** 关键字 */ + keyword?: string + /** 发布时间起(不含) */ + publishTimeStart?: InstantTime | null + /** 发布时间止(含) */ + publishTimeEnd?: InstantTime | null + /** 投递截止时间起(不含) */ + applyEndTimeStart?: InstantTime | null + /** 投递截止时间止(含) */ + applyEndTimeEnd?: InstantTime | null + /** 公司类型列表 */ + companyTypes?: string[] + /** 学历要求列表 */ + educationNames?: string[] + /** 招聘届数列表 */ + recruitYears?: number[] + /** 批次列表 */ + batchNames?: string[] + /** 岗位大类列表 */ + categoryNames?: string[] + /** 热招城市列表 */ + cityNames?: string[] + /** 行业列表 */ + industryNames?: string[] + /** 标签列表 */ + tagNames?: string[] +} + +/** 校招公告列表项 */ +export interface AnnouncementItem { + /** 公告ID */ + id: number + /** 公司ID */ + companyId: number + /** 公司名称 */ + companyName: string + /** 公司logo */ + logoUrl: string + /** 公告标题 */ + title: string + /** 专业要求 */ + majorRequire: string + /** 发布时间(毫秒时间戳) */ + publishTime: number | null + /** 投递结束时间 */ + applyEndTime: InstantTime | null + /** 投递截止描述 */ + applyEndDesc: string + /** 行业列表 */ + industryNames: string[] + /** 岗位大类列表 */ + categoryNames: string[] + /** 热招城市列表 */ + cityNames: string[] + /** 学历要求列表 */ + educationNames: string[] + /** 招聘届数列表 */ + recruitYears: number[] + /** 批次列表 */ + batchNames: string[] +} + +/** 校招公告分页数据 */ +export interface AnnouncementPageData { + /** 当前页码 */ + pageNum: number + /** 每页条数 */ + pageSize: number + /** 总记录数 */ + total: number + /** 数据列表 */ + list: AnnouncementItem[] +} + +/** + * 获取校招公告分页列表 + * POST /public/recruitAnnouncement/page + */ +export function fetchAnnouncementList(params: AnnouncementListParams = {}) { + return request.post>('/public/recruitAnnouncement/page', { + pageNum: params.pageNum ?? 1, + pageSize: params.pageSize ?? 10, + ...params, + }) +} + +// ==================== 校招公告详情接口 ==================== + +/** 校招公告详情数据 */ +export interface AnnouncementDetail { + /** 公告ID */ + id: number + /** 公司ID */ + companyId: number + /** 公司名称 */ + companyName: string + /** 公司logo */ + logoUrl: string + /** 公司类型 */ + companyType: string + /** 公告标题 */ + title: string + /** 公司简介 */ + companyIntro: string + /** 面向对象 */ + targetAudience: string + /** 专业要求 */ + majorRequire: string + /** 招聘岗位 */ + recruitPosition: string + /** 备注 */ + remark: string + /** 是否笔试 */ + writtenExam: string + /** 投递开始时间 */ + applyStartTime: InstantTime | null + /** 投递结束时间 */ + applyEndTime: InstantTime | null + /** 投递截止描述 */ + applyEndDesc: string + /** 专属内推码 */ + inviteCode: string + /** 信息来源说明 */ + source: string + /** 发布时间 */ + publishTime: InstantTime | null + /** 清洗状态 */ + cleanStatus: number + /** 状态 */ + status: number + /** 创建时间 */ + createTime: InstantTime | null + /** 更新时间 */ + updateTime: InstantTime | null + /** 行业列表 */ + industryNames: string[] + /** 岗位大类列表 */ + categoryNames: string[] + /** 热招城市列表 */ + cityNames: string[] + /** 学历要求列表 */ + educationNames: string[] + /** 招聘届数列表 */ + recruitYears: number[] + /** 批次列表 */ + batchNames: string[] + /** 标签列表 */ + tagNames: string[] +} + +/** + * 获取校招公告详情 + * GET /public/recruitAnnouncement/detail + */ +export function fetchAnnouncementDetail(id: string) { + return request.get>('/public/recruitAnnouncement/detail', { + params: { id }, + }) +} diff --git a/src/api/jobs.ts b/src/api/jobs.ts index 2bb4d9b..3665475 100644 --- a/src/api/jobs.ts +++ b/src/api/jobs.ts @@ -209,7 +209,7 @@ export function saveJobIntention(data: JobIntention) { * @param params 岗位列表查询参数 */ export function fetchJobList(params: JobListParams = {}) { - return request.post>('/job/list', { + return request.post>('/public/job/list', { pageNum: params.pageNum ?? 1, pageSize: params.pageSize ?? 15, ...params, diff --git a/src/assets/images/agent-setting/4-2.png b/src/assets/images/agent-setting/4-2.png new file mode 100644 index 0000000..3bb89d1 Binary files /dev/null and b/src/assets/images/agent-setting/4-2.png differ diff --git a/src/assets/images/agent-setting/edge-step1.png b/src/assets/images/agent-setting/edge-step1.png new file mode 100644 index 0000000..0bf7a02 Binary files /dev/null and b/src/assets/images/agent-setting/edge-step1.png differ diff --git a/src/assets/images/agent-setting/edge-step2.png b/src/assets/images/agent-setting/edge-step2.png new file mode 100644 index 0000000..2858267 Binary files /dev/null and b/src/assets/images/agent-setting/edge-step2.png differ diff --git a/src/assets/images/agent-setting/edge-step3.png b/src/assets/images/agent-setting/edge-step3.png new file mode 100644 index 0000000..f6b5e28 Binary files /dev/null and b/src/assets/images/agent-setting/edge-step3.png differ diff --git a/src/assets/images/job-count-card-icon01.png b/src/assets/images/job-count-card-icon01.png new file mode 100644 index 0000000..a4af676 Binary files /dev/null and b/src/assets/images/job-count-card-icon01.png differ diff --git a/src/assets/styles/auto.scss b/src/assets/styles/auto.scss index a9dd1b4..ea56dea 100644 --- a/src/assets/styles/auto.scss +++ b/src/assets/styles/auto.scss @@ -450,6 +450,9 @@ body:not(#_) { .flex-warp{ flex-wrap: wrap; } +.flex0{ + flex: 0; +} .flex1{ flex: 1; } diff --git a/src/assets/styles/components/agent-browser-install-guide.scss b/src/assets/styles/components/agent-browser-install-guide.scss index 647adae..3b22e05 100644 --- a/src/assets/styles/components/agent-browser-install-guide.scss +++ b/src/assets/styles/components/agent-browser-install-guide.scss @@ -1,6 +1,67 @@ // Agent浏览器插件安装步骤指引组件样式 @use '../variables' as *; +/* 组件最外层容器 */ +.agent-browser-guide { + font-size: 0.14rem; +} + +/* Tab切换栏 */ +.agent-browser-guide__tabs { + display: flex; + border-bottom: 1px solid #F0F0F0; + margin-bottom: 0.16rem; + padding: 0 0.32rem; +} + +/* 单个Tab */ +.agent-browser-guide__tab { + display: flex; + align-items: center; + gap: 0.06rem; + padding: 0.14rem 0.24rem; + font-size: 0.14rem; + font-weight: 450; + color: $text-middle; + cursor: pointer; + border-bottom: 2px solid transparent; + transition: color 0.2s, border-color 0.2s; + flex: 1; + justify-content: center; + + &:hover { + color: $accent; + } + + &--active { + color: $accent; + font-weight: 600; + border-bottom-color: $accent; + } + + svg { + flex-shrink: 0; + } +} + +/* 链接样式 */ +.agent-browser-guide__link { + color: $accent; + text-decoration: none; + + &:hover { + text-decoration: underline; + } +} + +/* URL截断文本 */ +.agent-browser-guide__url-text { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 2.8rem; +} + /* 步骤内容区域 */ .agent-browser-guide-drawer__body { flex: 1; @@ -74,6 +135,11 @@ border-radius: 0.12rem; height: 0.44rem; margin-left: 0.4rem; + + &--wide { + width: auto; + max-width: 100%; + } } .agent-browser-guide-drawer__download-name { @@ -96,6 +162,7 @@ font-weight: 620; cursor: pointer; transition: background 0.2s; + white-space: nowrap; &:hover { background: $accent-hover; diff --git a/src/assets/styles/components/job-page-header.scss b/src/assets/styles/components/job-page-header.scss index f0622c5..a3dcce5 100644 --- a/src/assets/styles/components/job-page-header.scss +++ b/src/assets/styles/components/job-page-header.scss @@ -1,86 +1,5 @@ @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: 620; - font-size: 0.10rem; - line-height: 0.13rem; - color: #8FB7BE; - } -} - .job-page-header { diff --git a/src/assets/styles/components/job-stats-overview.scss b/src/assets/styles/components/job-stats-overview.scss new file mode 100644 index 0000000..adeb9be --- /dev/null +++ b/src/assets/styles/components/job-stats-overview.scss @@ -0,0 +1,101 @@ +@use '../variables' as *; + +.job-stats-overview { + /* 固定宽度卡片 */ + box-sizing: border-box; + position: relative; + width: 3.25rem; + padding: 0.24rem 0.20rem; + background: linear-gradient(180deg, #EDF9FA 0%, #FFFFFF 20.63%); + border: 1px solid #F0F0F0; + box-shadow: 0 0.02rem 0.06rem rgba(15, 23, 42, 0.025); + border-radius: 0.12rem; + font-size: 0.14rem; + flex-shrink: 0; + display: flex; + align-items: center; + min-height: 1.8rem; + + /* 右上角装饰图片 */ + &__icon { + position: absolute; + top: 0.10rem; + right: 0.10rem; + width: 0.86rem; + height: 0.72rem; + object-fit: contain; + pointer-events: none; + } + + /* 内容纵向居中容器 */ + &__inner { + display: flex; + flex-direction: column; + justify-content: center; + gap: 0.32rem; + width: 100%; + } + + /* 标题区域 */ + &__header { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.04rem; + } + + &__title { + font-weight: 600; + font-size: 0.18rem; + line-height: 0.24rem; + color: #1A1A1A; + } + + &__date { + font-weight: 330; + font-size: 0.14rem; + line-height: 0.19rem; + color: #64748B; + } + + /* 数据指标行 */ + &__metrics { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.20rem; + } + + /* 单个指标 */ + &__metric-item { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.04rem; + } + + &__metric-num { + font-weight: 600; + font-size: 0.30rem; + line-height: 0.40rem; + color: $accent; + display: flex; + align-items: center; + text-align: center; + } + + &__metric-label { + font-weight: 330; + font-size: 0.14rem; + line-height: 0.19rem; + color: #6A7282; + text-align: center; + } + + /* 竖线分隔 */ + &__divider { + width: 0; + height: 0.52rem; + border-left: 1px solid #F0F0F0; + } +} diff --git a/src/assets/styles/components/login-dialog.scss b/src/assets/styles/components/login-dialog.scss new file mode 100644 index 0000000..def3715 --- /dev/null +++ b/src/assets/styles/components/login-dialog.scss @@ -0,0 +1,376 @@ +@use '../variables' as *; + +/* 登录弹窗遮罩层 */ +.login-dialog-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: var(--app-height, 100vh); + background: rgba(0, 0, 0, 0.45); + z-index: 2050; + display: flex; + align-items: center; + justify-content: center; +} + +/* 弹窗底座盒子 */ +.login-dialog-box { + font-size: 0.14rem; + position: relative; + width: 5.6rem; + min-height: 4.57rem; + background: radial-gradient(58.14% 30.59% at 100% 0%, #D2F2F3 0%, #FFFFFF 100%); + border: 1px solid #F0F0F0; + border-radius: 0.32rem; + padding: 0.48rem 0.6rem; + display: flex; + flex-direction: column; + align-items: center; +} + +/* 关闭按钮 */ +.login-dialog-box__close { + position: absolute; + top: 0.2rem; + right: 0.24rem; + width: 0.32rem; + height: 0.32rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 50%; + transition: background 0.2s; + + &:hover { + background: rgba(0, 0, 0, 0.05); + } + + svg { + width: 0.2rem; + height: 0.2rem; + } +} + +/* Logo 行 */ +.login-dialog-box__logo-row { + width: 100%; + margin-bottom: 0.4rem; + + img { + width: 1.6rem; + height: 0.4rem; + } +} + +/* 表单包裹 */ +.login-dialog-box__form-wrap { + display: flex; + flex-direction: column; + gap: 0.24rem; + width: 100%; +} + +/* 标题 */ +.login-dialog-box__heading { + display: flex; + flex-direction: column; + gap: 0.05rem; +} + +.login-dialog-box__title { + font-weight: 700; + font-size: 0.24rem; + line-height: 0.22rem; + color: #18181A; +} + +.login-dialog-box__subtitle { + font-weight: 400; + font-size: 0.14rem; + margin-top: 0.08rem; + margin-bottom: 0.08rem; + color: #616367; + + > span:nth-child(2) { + color: $accent; + } +} + +/* 手机号输入行 */ +.login-dialog-box__phone-row { + box-sizing: border-box; + display: flex; + align-items: center; + width: 100%; + height: 0.56rem; + background: #FFFFFF; + border: 1px solid #C8C9CD; + box-shadow: 0 0.08rem 0.20rem rgba(15, 83, 87, 0.05); + border-radius: 0.12rem; +} + +.login-dialog-box__phone-icon { + display: flex; + align-items: center; + justify-content: center; + width: 0.18rem; + height: 0.18rem; + margin-left: 0.16rem; + flex-shrink: 0; + + svg { + width: 0.18rem; + height: 0.18rem; + } +} + +.login-dialog-box__country-label { + margin-left: 0.06rem; + font-weight: 500; + font-size: 0.14rem; + line-height: 0.15rem; + color: #202124; + white-space: nowrap; +} + +.login-dialog-box__country-chevron { + display: flex; + align-items: center; + justify-content: center; + width: 0.20rem; + height: 0.20rem; + flex-shrink: 0; + cursor: pointer; + + svg { + width: 0.20rem; + height: 0.20rem; + } +} + +.login-dialog-box__input-divider { + width: 1px; + height: 0.30rem; + background: #E7F2F3; + margin: 0 0.16rem; + flex-shrink: 0; +} + +.login-dialog-box__phone-input { + flex: 1; + height: 100%; + border: none; + background: transparent; + font-weight: 400; + font-size: 0.14rem; + line-height: 0.15rem; + color: #202124; + outline: none; + + &::placeholder { + color: #939599; + } +} + +/* 发送验证码按钮 */ +.login-dialog-box__send-btn { + width: 100%; + height: 0.43rem; + background: linear-gradient(90deg, #4FC2C9 0%, #42A8B3 100%); + box-shadow: 0 0.16rem 0.30rem rgba(15, 83, 87, 0.15); + border-radius: 0.12rem; + border: none; + font-weight: 600; + font-size: 0.14rem; + line-height: 0.15rem; + color: #FFFFFF; + cursor: pointer; + transition: opacity 0.2s; + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + &:not(:disabled):hover { + opacity: 0.9; + } +} + +/* OTP 验证码输入 */ +.login-dialog-box__otp-wrap { + position: relative; + display: flex; + align-items: flex-start; + gap: 0.1rem; + width: 100%; + cursor: text; +} + +.login-dialog-box__otp-hidden-input { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + z-index: 2; + font-size: 0.16rem; + caret-color: transparent; +} + +.login-dialog-box__otp-box { + display: flex; + justify-content: center; + align-items: center; + width: 0.56rem; + height: 0.56rem; + background: #FAFBFC; + border: 1px solid #E2E8F0; + border-radius: 0.12rem; + transition: border-color 0.2s, background 0.2s; +} + +.login-dialog-box__otp-box--active { + background: #F3FFFD; + border: 1px solid $accent; +} + +.login-dialog-box__otp-box--filled { + background: #F3FFFD; + border: 2px solid $accent; +} + +.login-dialog-box__otp-digit { + font-weight: 700; + font-size: 0.24rem; + line-height: 0.29rem; + color: #132034; +} + +/* 闪烁光标 */ +.login-dialog-box__otp-cursor { + display: inline-block; + width: 2px; + height: 0.28rem; + background: $accent; + border-radius: 1px; + animation: login-dialog-otp-blink 1s ease-in-out infinite; +} + +@keyframes login-dialog-otp-blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +/* 验证码状态提示 */ +.login-dialog-box__sms-status { + width: 100%; + font-size: 0.13rem; + line-height: 0.16rem; + color: #64748B; + text-align: left; + margin-top: -0.14rem; + margin-bottom: -0.06rem; +} + +.login-dialog-box__sms-status--error { + color: #EF4444; +} + +/* 状态按钮(步骤二) */ +.login-dialog-box__status-btn { + width: 100%; + height: 0.43rem; + background: linear-gradient(90deg, $btn-dark 0%, $btn-dark-hover 100%); + border-radius: 0.12rem; + border: none; + font-weight: 600; + font-size: 0.14rem; + color: #FFFFFF; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + gap: 0.08rem; + transition: opacity 0.2s; + + &:disabled { + opacity: 0.7; + cursor: not-allowed; + } + + &:not(:disabled):hover { + opacity: 0.9; + } +} + +/* 登录中转圈动画 */ +.login-dialog-box__spinner { + display: inline-block; + width: 0.18rem; + height: 0.18rem; + border: 2px solid rgba(255, 255, 255, 0.3); + border-top-color: #FFFFFF; + border-radius: 50%; + animation: login-dialog-spin 0.7s linear infinite; +} + +@keyframes login-dialog-spin { + to { transform: rotate(360deg); } +} + +/* 协议勾选 */ +.login-dialog-box__agreement { + display: flex; + align-items: center; + gap: 0.06rem; +} + +.login-dialog-box__checkbox { + box-sizing: border-box; + width: 0.18rem; + height: 0.18rem; + background: #FFFFFF; + border: 1.5px solid #CBD5E1; + border-radius: 0.04rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.2s; + flex-shrink: 0; +} + +.login-dialog-box__checkbox--checked { + background: #4FC2C9; + border-color: #4FC2C9; +} + +.login-dialog-box__check-icon { + font-size: 0.11rem; + color: #FFFFFF; + font-weight: 700; + line-height: 1; +} + +.login-dialog-box__agreement-text { + font-weight: 400; + font-size: 0.12rem; + line-height: 0.15rem; + color: #616367; +} + +.login-dialog-box__agreement-link { + font-weight: 500; + font-size: 0.12rem; + line-height: 0.15rem; + color: #42A8B3; + cursor: pointer; + + &:hover { + text-decoration: underline; + } +} diff --git a/src/assets/styles/components/profile-page-content.scss b/src/assets/styles/components/profile-page-content.scss index 0ef808c..cae4105 100644 --- a/src/assets/styles/components/profile-page-content.scss +++ b/src/assets/styles/components/profile-page-content.scss @@ -349,4 +349,14 @@ color: #64748B; flex-shrink: 0; } + + // 个人概述文本 + &__summary-text { + font-size: 0.14rem; + line-height: 1.7; + color: #374151; + padding: 0 0.14rem 0.12rem; + white-space: pre-wrap; + word-break: break-word; + } } diff --git a/src/assets/styles/index.scss b/src/assets/styles/index.scss index 824a146..1a23d0e 100644 --- a/src/assets/styles/index.scss +++ b/src/assets/styles/index.scss @@ -12,12 +12,15 @@ @use './pages/login.scss'; @use './pages/mentor.scss'; @use './pages/mentor-detail.scss'; +@use './pages/announcement.scss'; +@use './pages/announcement-detail.scss'; // 组件样式 @use './components/hello-world.scss'; @use './components/side-nav.scss'; @use './components/ai-chat.scss'; @use './components/job-page-header.scss'; +@use './components/job-stats-overview.scss'; @use './components/job-goal-dialog.scss'; @use './components/settings-dialog.scss'; @use './components/member-dialog.scss'; @@ -45,6 +48,7 @@ @use './components/step-progress-overlay.scss'; @use './components/mentor-aside-panel.scss'; @use './components/agent-browser-install-guide.scss'; +@use './components/login-dialog.scss'; // 全局样式(优先级最高) @use './auto.scss'; diff --git a/src/assets/styles/pages/agent.scss b/src/assets/styles/pages/agent.scss index 5ee8063..76ac6d4 100644 --- a/src/assets/styles/pages/agent.scss +++ b/src/assets/styles/pages/agent.scss @@ -677,6 +677,78 @@ margin-bottom: 0.16rem; } + // 第2步投递偏好开关容器 + &__step2-pref-switches { + display: flex; + flex-direction: column; + gap: 0.12rem; + } + + // 第2步开关项 + &__step2-switch-item { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.2rem 0.24rem; + background: #F9FAFB; + border-radius: 0.12rem; + } + + // 第2步开关文字区 + &__step2-switch-text { + display: flex; + flex-direction: column; + gap: 0.04rem; + } + + // 第2步开关标签 + &__step2-switch-label { + font-size: 0.15rem; + font-weight: 500; + line-height: 0.21rem; + color: #1A1A1A; + } + + // 第2步开关描述 + &__step2-switch-desc { + font-size: 0.13rem; + font-weight: 330; + line-height: 0.18rem; + color: $text-middle; + } + + // 第2步自定义开关 + &__step2-toggle { + position: relative; + width: 0.44rem; + height: 0.24rem; + background: #E5E7EB; + border-radius: 0.12rem; + cursor: pointer; + transition: background 0.2s; + flex-shrink: 0; + + &--on { + background: $accent; + + .agent-page__step2-toggle-dot { + left: 0.22rem; + } + } + } + + // 第2步开关圆点 + &__step2-toggle-dot { + position: absolute; + width: 0.18rem; + height: 0.18rem; + left: 0.04rem; + top: 0.03rem; + background: #FFFFFF; + border-radius: 50%; + transition: left 0.2s; + } + // 第2步功能说明区域 — 全宽 &__step2-faq { width: 100%; @@ -744,13 +816,14 @@ padding: 0.12rem 0.16rem; min-width: 1.6rem; height: 0.43rem; - background: #F3F4F6; + background: #fff; border-radius: 0.12rem; cursor: pointer; box-sizing: border-box; user-select: none; transition: border-color 0.2s ease; - width: 1.6rem; + width: 100%; + border: 1px solid #F0F0F0; &:hover { border-color: darken($border-color, 10%); } } diff --git a/src/assets/styles/pages/announcement-detail.scss b/src/assets/styles/pages/announcement-detail.scss new file mode 100644 index 0000000..89c816b --- /dev/null +++ b/src/assets/styles/pages/announcement-detail.scss @@ -0,0 +1,532 @@ +@use '../variables' as *; + +// ==================== 校招公告详情页 ==================== + +.announcement-detail { + font-size: 0.14rem; + width: 100%; + height: var(--app-height, 100vh); + overflow: hidden; + min-width: 12.8rem; + + // 主内容区(与 JobDetail 一致,通过 margin-left 避开侧边栏) + &__content { + margin-left: var(--nav-width, 2.2rem); + flex: 1; + height: var(--app-height, 100vh); + box-sizing: border-box; + overflow: hidden; + background: $bg-main; + display: flex; + flex-direction: column; + font-size: 0.14rem; + } + + // 页面标题 + &__page-title { + font-size: 0.18rem; + font-weight: 600; + color: $text-dark; + line-height: 0.24rem; + } + + &__page-subtitle { + font-size: 0.12rem; + color: $text-middle; + line-height: 0.16rem; + } + + // 返回按钮行 + &__back-row { + padding: 0 0.24rem; + margin-bottom: 0.12rem; + } + + &__back-btn { + display: flex; + align-items: center; + gap: 0.04rem; + background: none; + border: none; + cursor: pointer; + font-size: 0.14rem; + color: $text-middle; + padding: 0; + + &:hover { + color: $accent; + } + } + + &__back-icon { + width: 0.16rem; + height: 0.16rem; + } + + // 主体内容可滚动区域 + &__body { + flex: 1; + overflow-y: auto; + padding: 0 0.24rem 0.24rem; + } + + // 两列布局(公司卡片下方) + &__layout { + display: flex; + gap: 0.12rem; + align-items: flex-start; + } + + // 左侧主详情 + &__left { + flex: 1; + min-width: 0; + background: #fff; + padding: 0.24rem 0.32rem; + border-radius: 0.08rem; + } + + // 右侧边栏 + &__right { + width: 4.24rem; + flex-shrink: 0; + } +} + +// ==================== 顶部公司信息卡片(独占一行) ==================== + +.announcement-detail__company-card { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.24rem 0.32rem; + background: $bg-white; + border: 1px solid #F0F0F0; + box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.04); + border-radius: 0.08rem; + margin-bottom: 0.12rem; +} + +.announcement-detail__company-left { + display: flex; + align-items: center; + gap: 0.16rem; +} + +.announcement-detail__company-logo { + width: 0.66rem; + height: 0.66rem; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; + + &--default { + display: flex; + align-items: center; + justify-content: center; + background: #EDF9FA; + } +} + +.announcement-detail__company-info { + display: flex; + flex-direction: column; + gap: 0.12rem; +} + +.announcement-detail__company-name-row { + display: flex; + align-items: center; + gap: 0.16rem; +} + +.announcement-detail__company-name { + font-size: 0.24rem; + font-weight: 600; + color: $text-dark; + line-height: 0.32rem; +} + +.announcement-detail__company-tags { + display: flex; + align-items: flex-start; + gap: 0.12rem; +} + +.announcement-detail__tag { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.03rem 0.06rem; + font-size: 0.12rem; + line-height: 0.16rem; + border-radius: 0.04rem; + + &--type { + background: #FEF3F2; + color: #B42318; + } + + &--gray { + background: #F3F4F6; + color: $text-middle; + } +} + +.announcement-detail__company-right { + display: flex; + align-items: center; + gap: 0.16rem; +} + +.announcement-detail__btn-outline { + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + padding: 0.08rem 0.16rem; + font-size: 0.14rem; + font-weight: 450; + color: $accent; + background: transparent; + border: 1px solid $accent; + border-radius: 0.08rem; + cursor: pointer; + white-space: nowrap; + + &:hover { + background: #EDF9FA; + } +} + +.announcement-detail__btn-primary { + box-sizing: border-box; + display: flex; + align-items: center; + justify-content: center; + padding: 0.08rem 0.16rem; + font-size: 0.14rem; + font-weight: 450; + color: #FFFFFF; + background: $accent; + border: 1px solid $accent; + border-radius: 0.08rem; + cursor: pointer; + white-space: nowrap; + + &:hover { + background: $accent-hover; + border-color: $accent-hover; + } +} + +// ==================== 岗位速览卡片 ==================== + +.announcement-detail__overview-card { + display: flex; + flex-direction: column; + padding: 0.16rem 0.24rem; + gap: 0.16rem; + background: linear-gradient(180deg, #EDF9FA 0%, #FFFFFF 20.63%); + border: 1px solid #AEE6EA; + border-radius: 0.08rem; + margin-bottom: 0.24rem; +} + +.announcement-detail__overview-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.announcement-detail__overview-title-row { + display: flex; + align-items: flex-end; + gap: 0.12rem; +} + +.announcement-detail__overview-title { + font-size: 0.16rem; + font-weight: 600; + color: $text-dark; + line-height: 0.21rem; +} + +.announcement-detail__overview-date { + font-size: 0.12rem; + font-weight: 330; + color: $text-middle; + line-height: 0.16rem; +} + +.announcement-detail__overview-deadline { + font-size: 0.14rem; + font-weight: 380; + color: $text-dark; + line-height: 0.19rem; +} + +.announcement-detail__overview-body { + display: flex; + gap: 0.24rem; +} + +.announcement-detail__overview-fields { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + flex-wrap: wrap; +} + +// 每行两个字段横排,align-items: stretch 保证同行等高 +.announcement-detail__overview-row { + display: flex; + align-items: stretch; + gap: 0.16rem; +} + +.announcement-detail__overview-direction { + flex-shrink: 0; + display: flex; + flex-direction: column; + gap: 0.04rem; +} + +.announcement-detail__overview-field { + display: flex; + flex-direction: column; + gap: 0.04rem; +} + +.announcement-detail__overview-label { + font-size: 0.12rem; + font-weight: 330; + color: $text-middle; + line-height: 0.16rem; +} + +.announcement-detail__overview-value { + font-size: 0.14rem; + font-weight: 380; + color: $text-dark; + line-height: 0.19rem; +} + +// ==================== 公告标题 ==================== + +.announcement-detail__main-title { + font-size: 0.24rem; + font-weight: 600; + color: $text-dark; + line-height: 0.32rem; + margin-bottom: 0.24rem; +} + +// ==================== 详情正文段落 ==================== + +.announcement-detail__section { + margin-bottom: 0.16rem; +} + +.announcement-detail__section-title { + font-size: 0.18rem; + font-weight: 600; + color: $accent; + line-height: 0.24rem; + margin-bottom: 0.16rem; +} + +.announcement-detail__section-content { + font-size: 0.14rem; + font-weight: 330; + color: $text-dark; + line-height: 0.19rem; + white-space: pre-wrap; + word-break: break-all; +} + +// ==================== 右侧边栏卡片 ==================== + +.announcement-detail__aside-card { + background: $bg-white; + border: 1px solid #F0F0F0; + box-shadow: 0px 1px 4px rgba(0, 0, 0, 0.04); + border-radius: 0.08rem; + padding: 0.24rem 0.32rem; + margin-bottom: 0.12rem; +} + +.announcement-detail__aside-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.24rem; +} + +.announcement-detail__aside-title { + font-size: 0.18rem; + font-weight: 600; + color: $accent; + line-height: 0.24rem; +} + +.announcement-detail__aside-more { + display: flex; + align-items: center; + gap: 0.04rem; + font-size: 0.14rem; + font-weight: 450; + color: $text-middle; + cursor: pointer; + + svg { + width: 0.19rem; + height: 0.19rem; + } + + &:hover { + color: $accent; + } +} + +.announcement-detail__aside-list { + display: flex; + flex-direction: column; + gap: 0.16rem; +} + +// ==================== 企业其他岗位列表项 ==================== + +.announcement-detail__job-item { + position: relative; + display: flex; + align-items: flex-start; + justify-content: space-between; + padding-bottom: 0.16rem; +} + +.announcement-detail__job-item-main { + flex: 1; + min-width: 0; +} + +.announcement-detail__job-item-title { + font-size: 0.16rem; + font-weight: 600; + color: $text-dark; + line-height: 0.21rem; + display: block; + margin-bottom: 0.08rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 2.56rem; +} + +.announcement-detail__job-item-tags { + display: flex; + align-items: center; + gap: 0.08rem; +} + +.announcement-detail__job-item-score { + text-align: right; + flex-shrink: 0; +} + +.announcement-detail__job-item-score-num { + display: block; + font-size: 0.22rem; + font-weight: 600; + line-height: 0.29rem; +} + +.announcement-detail__job-item-score-label { + display: block; + font-size: 0.12rem; + font-weight: 450; + line-height: 0.16rem; + text-align: center; +} + +.announcement-detail__job-divider { + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 0; + border-bottom: 1px solid #F0F0F0; +} + +// ==================== 通用灰色chip ==================== + +.announcement-detail__chip { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.03rem 0.06rem; + background: #F3F4F6; + border-radius: 0.04rem; + font-size: 0.12rem; + font-weight: 380; + color: $text-middle; + line-height: 0.16rem; + white-space: nowrap; +} + +// ==================== 校招推荐列表项 ==================== + +.announcement-detail__rec-item { + display: flex; + align-items: flex-start; + gap: 0.1rem; + position: relative; + padding-bottom: 0.16rem; +} + +.announcement-detail__rec-logo { + width: 0.4rem; + height: 0.4rem; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; + + &--default { + display: flex; + align-items: center; + justify-content: center; + background: #EDF9FA; + } +} + +.announcement-detail__rec-info { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 0.04rem; +} + +.announcement-detail__rec-company { + font-size: 0.16rem; + font-weight: 600; + color: #111827; + line-height: 0.21rem; +} + +.announcement-detail__rec-positions { + font-size: 0.14rem; + font-weight: 330; + color: $text-middle; + line-height: 0.19rem; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.announcement-detail__rec-tags { + display: flex; + align-items: flex-start; + gap: 0.04rem; +} diff --git a/src/assets/styles/pages/announcement.scss b/src/assets/styles/pages/announcement.scss new file mode 100644 index 0000000..b40209e --- /dev/null +++ b/src/assets/styles/pages/announcement.scss @@ -0,0 +1,555 @@ +// 校招公告页面样式 +@use '../variables' as *; + +.announcement-page { + display: flex; + + &__content { + margin-left: var(--nav-width, 2.2rem); + flex: 1; + padding-bottom: 0.18rem; + height: var(--app-height, 100vh); + box-sizing: border-box; + overflow: hidden; + background: $bg-main; + font-size: 0.14rem; + } + + // 页面标题 + &__title { + font-size: 0.24rem; + font-weight: 700; + color: $text-dark; + margin: 0; + padding: 0; + line-height: 1.1; + } + + &__subtitle { + font-size: 0.13rem; + color: $text-light; + padding: 0; + } + + // 筛选条件外层 + &__filters-bar { + border-radius: 0.12rem; + margin: 0 0.24rem 0.12rem 0.24rem; + } + + // 筛选面板 + 统计卡片横向排列 + &__filters-row { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 0.12rem; + } + + // 筛选面板 + &__filters-panel { + flex: 1; + min-width: 0; + background: $bg-white; + border: 1px solid #F0F0F0; + box-shadow: 0px 2px 6px rgba(15, 23, 42, 0.025); + border-radius: 0.12rem; + padding: 0.12rem 0.16rem; + display: flex; + flex-direction: column; + gap: 0.12rem; + } + + // 筛选行 + &__filter-row { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + gap: 0.08rem 0.16rem; + background: $bg-white; + } + + &__filter-row-inner { + display: flex; + flex-direction: row; + align-items: center; + gap: 0.16rem; + flex: 1; + min-width: 0; + } + + // 筛选行标题 + &__filter-row-label { + font-size: 0.12rem; + line-height: 0.16rem; + font-weight: 380; + color: $text-middle; + flex-shrink: 0; + white-space: nowrap; + } + + // chip组外层 + &__filter-chips-wrap { + display: flex; + align-items: flex-start; + gap: 0.08rem; + flex: 1; + min-width: 0; + + > .industry-selector, + > .job-category-selector, + > .region-selector { + flex-shrink: 0; + margin-left: auto; + align-self: flex-end; + } + } + + // chip组 + &__filter-chips { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + align-content: center; + gap: 0.08rem; + flex: 1; + min-width: 0; + } + + // 筛选chip + &__filter-chip { + display: flex; + justify-content: center; + align-items: center; + padding: 0.04rem 0.08rem; + background: #F3F4F6; + border-radius: 0.04rem; + font-size: 0.12rem; + line-height: 0.16rem; + font-weight: 330; + color: $text-dark; + cursor: pointer; + white-space: nowrap; + user-select: none; + transition: all 0.15s; + border: 1px solid transparent; + + &:hover { + background: #E8F8F9; + color: $accent; + } + + &--active { + background: #F4FBFB; + border-color: $accent; + color: $accent; + font-weight: 600; + } + + &--active-solid { + background: $accent; + color: #FFFFFF; + font-weight: 600; + border-color: $accent; + } + } + + // 底部筛选行 + &__filter-row--bottom {} + + // 下拉按钮组容器 + &__filter-dropdowns { + display: flex; + align-items: center; + gap: 0.08rem; + flex-wrap: wrap; + } + + // 下拉按钮项 + &__dropdown-item { + display: flex; + align-items: center; + gap: 0.02rem; + padding: 0.04rem 0.08rem; + border: 1px solid #F0F0F0; + border-radius: 0.04rem; + font-size: 0.12rem; + line-height: 0.16rem; + font-weight: 330; + color: $text-dark; + cursor: pointer; + white-space: nowrap; + position: relative; + user-select: none; + transition: all 0.15s; + + &:hover { + border-color: $accent; + color: $accent; + } + + .el-range-separator { + padding-bottom: 0.03rem !important; + } + } + + &__dropdown-arrow { + width: 0.12rem; + height: 0.12rem; + color: $text-middle; + flex-shrink: 0; + } + + // 下拉菜单面板 + &__filter-dropdown { + position: absolute; + top: calc(100% + 0.06rem); + left: 0; + background: $bg-white; + border: 1px solid #E8E8E8; + border-radius: 0.08rem; + padding: 0.04rem 0; + box-shadow: 0 0.04rem 0.12rem rgba(0, 0, 0, 0.08); + z-index: 20; + min-width: 100%; + } + + // 下拉菜单选项 + &__filter-dropdown-item { + padding: 0.06rem 0.16rem; + font-size: 0.12rem; + color: $text-dark; + cursor: pointer; + white-space: nowrap; + transition: all 0.15s; + + &:hover { + background: $theme-color; + color: $accent-hover; + } + + &--active { + color: $accent; + font-weight: 600; + } + } + + // 清空筛选按钮 + &__clear-filters { + font-size: 0.12rem; + color: $text-middle; + cursor: pointer; + white-space: nowrap; + flex-shrink: 0; + + &:hover { + color: $accent; + } + } + + // 搜索框 + &__search-box { + display: flex; + align-items: center; + background: $bg-white; + border-radius: 0.08rem; + padding: 0.10rem 0.14rem; + border: 1px solid #F0F0F0; + + &:focus-within { + border-color: $accent; + } + } + + &__search-svg { + width: 0.14rem; + height: 0.14rem; + color: $text-light; + margin-right: 0.08rem; + flex-shrink: 0; + cursor: pointer; + } + + &__search-input { + flex: 1; + background: transparent; + border: none; + outline: none; + color: $text-dark; + font-size: 0.12rem; + + &::placeholder { + color: $text-light; + } + } + + // 热门搜索标签 + &__hot-search-label { + font-size: 0.12rem; + color: $text-middle; + white-space: nowrap; + margin-right: 0.07rem; + } + + &__hot-search-tag { + display: inline-flex; + align-items: center; + justify-content: center; + height: 0.24rem; + padding: 0 0.1rem; + border-radius: 0.04rem; + font-size: 0.12rem; + color: $accent; + background: $bg-white; + cursor: pointer; + white-space: nowrap; + user-select: none; + transition: all 0.15s; + margin-right: 0.07rem; + + &:last-child { + margin-right: 0; + } + + &:hover, + &--active { + background: $accent; + color: #FFFFFF; + } + } + + // 列表区域 + &__list { + overflow-y: auto; + height: calc(var(--app-height, 100vh) - 3.4rem); + padding: 0 0.24rem 0.2rem; + } + + // 表头 + &__table-header { + display: flex; + align-items: center; + padding: 0.10rem 0.12rem; + background: #F4FBFB; + border-radius: 0.08rem 0.08rem 0 0; + border-bottom: 1px solid #F0F0F0; + font-size: 0.12rem; + font-weight: 600; + color: $text-dark; + position: sticky; + top: 0; + z-index: 5; + gap: 0.2rem; + >span{ + flex: 1; + text-align: center; + } + } + + // 表格行 + &__table-row { + display: flex; + align-items: center; + padding: 0.12rem; + background: $bg-white; + border-bottom: 1px solid #F6F6F6; + font-size: 0.12rem; + color: $text-dark; + transition: background 0.15s; + gap: 0.2rem; + + &:hover { + background: $theme-color; + } + + &:last-child { + border-bottom: none; + } + >span{ + flex: 1; + } + >div{ + flex: 1; + } + } + + // 表头列宽 + &__th, &__td { + flex-shrink: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &__th--time, &__td--time { min-width: 0.7rem; } + &__th--company, &__td--company { width: 1.8rem; flex-shrink: 1; } + &__th--title, &__td--title { width: 1.8rem; flex: 1; min-width: 1rem; } + &__th--position, &__td--position { width: 1.5rem; } + &__th--target, &__td--target { width: 1.2rem; } + &__th--major, &__td--major { width: 1.6rem; } + &__th--batch, &__td--batch { width: 1rem; } + &__th--deadline, &__td--deadline { width: 0.9rem; } + + // 公司信息列 + &__td--company { + white-space: normal; + } + + &__company-info { + display: flex; + align-items: center; + gap: 0.08rem; + } + + &__company-logo { + width: 0.32rem; + height: 0.32rem; + border-radius: 0.06rem; + border: 1px solid #F4F4F4; + flex-shrink: 0; + object-fit: cover; + + &--default { + display: flex; + align-items: center; + justify-content: center; + background: $bg-white; + } + } + + &__company-text { + min-width: 0; + } + + &__company-name { + font-size: 0.13rem; + font-weight: 500; + color: $text-dark; + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &__company-tags { + display: flex; + gap: 0.04rem; + margin-top: 0.02rem; + flex-wrap: wrap; + } + + &__company-tag { + font-size: 0.11rem; + padding: 0.01rem 0.04rem; + border-radius: 0.02rem; + white-space: nowrap; + + &--industry { + background: #EDF9FA; + color: $accent; + } + } + + // 多行单元格 + &__td--position, &__td--target { + white-space: normal; + display: flex; + flex-direction: column; + gap: 0.02rem; + } + + &__cell-line { + font-size: 0.12rem; + color: $text-dark; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &__cell-sub { + font-size: 0.11rem; + color: $text-light; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + &__recruit-year { + font-size: 0.12rem; + color: #175CD3; + background: #EFF8FF; + padding: 0 0.08rem; + border-radius: 0.04rem; + display: inline-block; + align-self: flex-start; + height: 0.24rem; + line-height: 0.24rem; + } + + // 空状态 + &__empty { + text-align: center; + font-size: 0.14rem; + color: $text-light; + } + + &__empty-img { + width: 1.24rem; + height: 1.3rem; + display: block; + margin: 1rem auto 0.2rem; + } + + &__empty-title { + font-size: 0.14rem; + color: $text-light; + margin-bottom: 0.08rem; + } + + &__empty-subtitle { + font-size: 0.14rem; + color: $text-light; + } + + &__empty-link { + color: $accent; + text-decoration: underline; + cursor: pointer; + + &:hover { + opacity: 0.75; + } + } + + // 加载状态 + &__loading-box { + display: flex; + align-items: center; + justify-content: center; + gap: 0.08rem; + padding: 0.24rem 0; + font-size: 0.13rem; + color: $text-light; + } + + &__loading-spinner { + width: 0.18rem; + height: 0.18rem; + border: 2px solid rgba(0, 0, 0, 0.1); + border-top-color: $accent; + border-radius: 50%; + animation: announcement-spin 0.6s linear infinite; + } + + @keyframes announcement-spin { + to { transform: rotate(360deg); } + } + + &__loading-more { + text-align: center; + padding: 0.16rem 0; + font-size: 0.12rem; + color: $text-light; + } +} diff --git a/src/assets/styles/pages/jobs.scss b/src/assets/styles/pages/jobs.scss index 8f3797b..2a2d3ec 100644 --- a/src/assets/styles/pages/jobs.scss +++ b/src/assets/styles/pages/jobs.scss @@ -83,6 +83,14 @@ margin: 0 0.24rem 0.12rem 0.24rem; } + // 筛选面板 + 统计卡片横向排列容器 + &__filters-row { + display: flex; + flex-direction: row; + align-items: flex-start; + gap: 0.12rem; + } + // AI 推荐提示条外层 &__ai-tip-wrapper { position: relative; @@ -198,6 +206,8 @@ // 筛选面板 &__filters-panel { + flex: 1; + min-width: 0; background: $bg-white; border: 1px solid #F0F0F0; box-shadow: 0px 2px 6px rgba(15, 23, 42, 0.025); @@ -295,14 +305,14 @@ background: #F4FBFB; border-color: $accent; color: $accent; - font-weight: 520; + font-weight: 600; } // 选中态(实心品牌色背景,用于"全部/全国"按钮选中时) &--active-solid { background: $accent; color: #FFFFFF; - font-weight: 520; + font-weight: 600; border-color: $accent; } } @@ -390,7 +400,7 @@ &__deep-match-btn { padding: 0 0.08rem; height: 0.20rem; - line-height: 0.16rem; + line-height: 0.20rem; border: none; border-radius: 0.04rem; background: linear-gradient(128.17deg, #52CAD1 0%, #82D79B 100%); @@ -398,8 +408,6 @@ font-size: 0.12rem; font-weight: 500; cursor: pointer; - white-space: nowrap; - flex-shrink: 0; transition: opacity 0.2s; &:hover { @@ -529,7 +537,6 @@ border-radius: 0.08rem; padding: 0.10rem 0.14rem; border: 1px solid #F0F0F0; - margin-top: 0.12rem; &:focus-within { border-color: $accent; @@ -558,6 +565,42 @@ } } + // 热门搜索标签 label + &__hot-search-label { + font-size: 0.12rem; + color: $text-middle; + white-space: nowrap; + margin-right: 0.07rem; + } + + // 热门搜索标签 + &__hot-search-tag { + display: inline-flex; + align-items: center; + justify-content: center; + height: 0.24rem; + padding: 0 0.1rem; + border-radius: 0.04rem; + font-size: 0.12rem; + color: $accent; + background: $bg-white; + cursor: pointer; + white-space: nowrap; + user-select: none; + transition: all 0.15s; + margin-right: 0.07rem; + + &:last-child { + margin-right: 0; + } + + &:hover, + &--active { + background: $accent; + color: #FFFFFF; + } + } + // 职位列表(用 calc 减去上方头部+筛选栏大致高度,保证列表独立滚动) &__list { gap: 0; @@ -619,7 +662,7 @@ // 岗位标题 &__card-title { font-size: 0.16rem; - font-weight: 520; + font-weight: 600; color: #1A1A1A; line-height: 0.21rem; white-space: nowrap; @@ -662,7 +705,7 @@ &__card-match-score { display: block; font-size: 0.22rem; - font-weight: 520; + font-weight: 600; line-height: 0.29rem; text-align: center; } @@ -957,6 +1000,53 @@ z-index: 10; } + // 未登录时底部登录提示 + &__login-prompt { + text-align: center; + margin-top: -0.3rem; + padding-top: 0.4rem; + padding-bottom: 1.6rem; + background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, #FFFFFF 25%); + position: relative; + z-index: 9999; + } + + &__login-prompt-title { + font-size: 0.18rem; + font-weight: 520; + color: $accent; + margin-bottom: 0.12rem; + } + + &__login-prompt-desc { + font-size: 0.14rem; + font-weight: 330; + color: #6A7282; + margin-bottom: 0.2rem; + } + + &__login-prompt-btn { + display: inline-flex; + justify-content: center; + align-items: center; + padding: 0.08rem 0.32rem; + width: 1.8rem; + height: 0.37rem; + font-size: 0.14rem; + font-weight: 450; + color: #fff; + background: $btn-dark; + border: 1px solid $btn-dark; + border-radius: 0.08rem; + cursor: pointer; + transition: background 0.2s; + + &:hover { + background: $btn-dark-hover; + border-color: $btn-dark-hover; + } + } + // 列表底部加载盒子 &__loading-box { display: flex; diff --git a/src/components/AgentBrowserInstallGuide.vue b/src/components/AgentBrowserInstallGuide.vue index 7b4243a..cbd8c6a 100644 --- a/src/components/AgentBrowserInstallGuide.vue +++ b/src/components/AgentBrowserInstallGuide.vue @@ -1,158 +1,225 @@ - + diff --git a/src/components/LoginDialog.vue b/src/components/LoginDialog.vue index 59ce36c..429fb54 100644 --- a/src/components/LoginDialog.vue +++ b/src/components/LoginDialog.vue @@ -1,152 +1,282 @@ diff --git a/src/components/ProfilePageContent.vue b/src/components/ProfilePageContent.vue index bc32ebe..d77f84a 100644 --- a/src/components/ProfilePageContent.vue +++ b/src/components/ProfilePageContent.vue @@ -64,6 +64,30 @@
+ +
+ + +
+ +
+
@@ -305,6 +329,7 @@ import ResumeExpInlineEdit from '@/components/ResumeExpInlineEdit.vue' /** Tab 导航列表 */ const tabList = [ { key: 'info', label: '个人信息' }, + { key: 'summary', label: '个人概述' }, { key: 'education', label: '教育经历' }, { key: 'work', label: '工作经历' }, { key: 'internship', label: '实习经历' }, @@ -320,6 +345,7 @@ const activeTab = ref('info') /** 各模块的 DOM 引用 */ const scrollContainerRef = ref(null) const infoRef = ref(null) +const summaryRef = ref(null) const educationRef = ref(null) const workRef = ref(null) const internshipRef = ref(null) @@ -331,6 +357,7 @@ const certificateRef = ref(null) /** 模块 key 与 ref 的映射 */ const sectionRefMap: Record = { info: infoRef, + summary: summaryRef, education: educationRef, work: workRef, internship: internshipRef, @@ -360,7 +387,8 @@ interface ProfileData { regionCode: string portfolioUrl: string wechat?: string - education: Array<{ school: string; major: string; studyType: number; degree: number; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }> + summary?: string + education: Array<{ school: string; major: string; studyType: string | number; degree: string | number; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }> works: Array<{ companyName: string; position: string; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }> internships: Array<{ companyName: string; position: string; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }> projects: Array<{ projectName: string; companyName: string; role: string; startDate: string; endDate: string; description: Array<{ id: string; text: string }> }> @@ -401,11 +429,15 @@ const editingExpIndex = ref('') /** 当前添加经历的模块 */ const addingModule = ref('') -/** 获取学历文本 */ -function getDegreeText(studyType: number, degree: number): string { - const studyTypeText = studyType === 0 ? '全日制' : '非全日制' +/** 获取学历文本 — 兼容数字和中文字符串 */ +function getDegreeText(studyType: string | number, degree: string | number): string { + // studyType 可能是 0/1 数字 或 "全日制"/"非全日制" 字符串 + const studyTypeMap: Record = { 0: '全日制', 1: '非全日制' } + const studyTypeText = typeof studyType === 'number' ? (studyTypeMap[studyType] || '') : (studyType || '') + // degree 可能是 1/2/3/4 数字 或 "大专"/"本科"/"硕士"/"博士" 字符串 const degreeMap: Record = { 1: '大专', 2: '本科', 3: '硕士', 4: '博士' } - return `${studyTypeText}${degreeMap[degree] || ''}` + const degreeText = typeof degree === 'number' ? (degreeMap[degree] || '') : (degree || '') + return `${studyTypeText}${degreeText}` } /** 打开模块编辑(个人信息/技能/证书) */ @@ -427,6 +459,8 @@ function startEdit(module: string) { editInitialData.value = { skills: [...(props.profile.skills || [])] } } else if (module === 'certificate') { editInitialData.value = { certificates: [...(props.profile.certificates || [])] } + } else if (module === 'summary') { + editInitialData.value = { summary: props.profile.summary || '' } } } diff --git a/src/components/SideNav.vue b/src/components/SideNav.vue index c822a21..07eca2d 100644 --- a/src/components/SideNav.vue +++ b/src/components/SideNav.vue @@ -22,6 +22,7 @@ > + ', + // 索引3 — 第四个菜单项(待替换) + '', // 索引1 — 第二个菜单项(待替换) '', // 索引2 — 第三个菜单项(待替换) '', - // 索引3 — 第四个菜单项(待替换) - '', // 索引4 — 第五个菜单项(待替换) '', // 索引5 — 第六个菜单项(待替换) - '', + '', ] /** @@ -829,8 +830,9 @@ async function handleNav(item: MenuItem) { return } - // Jobs 等静态页面 — 无需登录验证,直接跳转 - if (staticNames.includes(item.name)) { + // 不需要登录的白名单页面 — 直接跳转 + const publicNavNames = ['Jobs', 'JobDetail', 'Announcement', 'AnnouncementDetail', 'MentorDetail'] + if (staticNames.includes(item.name) || publicNavNames.includes(item.name)) { router.push(item.path) return } diff --git a/src/plugins/remAdapt.ts b/src/plugins/remAdapt.ts index 607aa9f..cd171d7 100644 --- a/src/plugins/remAdapt.ts +++ b/src/plugins/remAdapt.ts @@ -13,7 +13,7 @@ const remAdaptPlugin: Plugin = { // 检测是否为移动端/小屏 const isMobile = (): boolean => { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) - || window.innerWidth < 1200 + } // 重新计算缩放 diff --git a/src/router/dynamicRoutes.ts b/src/router/dynamicRoutes.ts index 02180f8..969a3bf 100644 --- a/src/router/dynamicRoutes.ts +++ b/src/router/dynamicRoutes.ts @@ -10,11 +10,12 @@ import type { MenuItemRaw } from '@/api/menu' * 【新增页面时】在这里加一条映射即可,后端返回对应的 key 就能自动注册路由 */ const componentMap: Record Promise> = { - 'Jobs': () => import('@/views/Jobs.vue'), - 'Agent': () => import('@/views/Agent.vue'), - 'Profile': () => import('@/views/Profile.vue'), - 'Resume': () => import('@/views/Resume.vue'), - 'Mentor': () => import('@/views/Mentor.vue'), + 'Jobs': () => import('@/views/Jobs.vue'), + 'Agent': () => import('@/views/Agent.vue'), + 'Profile': () => import('@/views/Profile.vue'), + 'Resume': () => import('@/views/Resume.vue'), + 'Mentor': () => import('@/views/Mentor.vue'), + 'Announcement': () => import('@/views/Announcement.vue'), } /** @@ -23,7 +24,7 @@ const componentMap: Record Promise> = { * 如果后端返回了一个 component 字符串在映射表里找不到,会跳过该条路由并打印警告 * 已在静态路由中注册的路径(如 /jobs)不再重复注册 */ -const STATIC_PATHS = ['/', '/m', '/jobs', '/resume/:id', '/jobs/:id', '/mentor/:id'] +const STATIC_PATHS = ['/', '/m', '/jobs', '/resume/:id', '/jobs/:id', '/mentor/:id', '/announcement/:id'] export function buildDynamicRoutes(menus: MenuItemRaw[]): RouteRecordRaw[] { const routes: RouteRecordRaw[] = [] diff --git a/src/router/index.ts b/src/router/index.ts index fa57f9e..ec7d98d 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -3,8 +3,9 @@ import type { RouteRecordRaw } from 'vue-router' import store from '@/stores' import { checkLogin } from '@/api/auth' import { fetchMemberStatus } from '@/api/member' -import { fetchProfile } from '@/api/profile' -import { ENABLE_FORCE_RESUME_UPLOAD } from '@/business.config' +// 【已禁用】强制简历上传机制相关引用 +// import { fetchProfile } from '@/api/profile' +// import { ENABLE_FORCE_RESUME_UPLOAD } from '@/business.config' /** * 静态路由 — 不需要权限,应用启动就注册 @@ -18,7 +19,6 @@ const staticRoutes: RouteRecordRaw[] = [ path: '/jobs', name: 'Jobs', component: () => import('@/views/Jobs.vue'), - meta: { requiresAuth: true }, }, { path: '/resume/:id', @@ -36,6 +36,11 @@ const staticRoutes: RouteRecordRaw[] = [ name: 'MentorDetail', component: () => import('@/views/MentorDetail.vue'), }, + { + path: '/announcement/:id', + name: 'AnnouncementDetail', + component: () => import('@/views/AnnouncementDetail.vue'), + }, { path: '/privacy', name: 'PrivacyPolicy', @@ -116,76 +121,63 @@ router.beforeEach(async (to, _from, next) => { return } - // 需要鉴权的路由,每次都通过接口校验登录状态 - if (to.meta?.requiresAuth) { + // 不需要登录即可访问的页面白名单(首页、岗位列表/详情、校招公告列表/详情等) + const publicPages: string[] = [ + 'Home', 'HomeMobile', 'Login', 'SuperRjtfoewjksfewjLogin', + 'Jobs', 'JobDetail', 'Announcement', 'AnnouncementDetail', + 'MentorDetail', 'PrivacyPolicy', + ] + const isPublicPage = publicPages.includes(to.name as string) + + // 非白名单页面需要鉴权 — 通过接口校验登录状态 + if (!isPublicPage) { try { const res = await checkLogin() lastCheckTime = Date.now() if (res.code === '0' && res.data === true) { - // Cookie 有效,同步前端状态并放行 + // Cookie 有效,同步前端状态 store.commit('SET_AUTHENTICATED', true) - // 检查个人资料是否为空(强制简历上传机制) - if (ENABLE_FORCE_RESUME_UPLOAD) { - try { - const profileRes = await fetchProfile() - if (profileRes.code === '0' && !profileRes.data) { - next({ name: 'Login', query: { resumeUpload: '1' } }) - return - } - } catch { /* 接口异常不阻断 */ } - } - next() - } else { - // 未登录或 Cookie 失效 — 跳转登录页 - store.commit('SET_AUTHENTICATED', false) - next({ name: 'Login', query: { redirect: to.fullPath } }) - } - } catch { - // 请求异常(网络错误等),也视为未登录 - store.commit('SET_AUTHENTICATED', false) - next({ name: 'Login', query: { redirect: to.fullPath } }) - } - return - } - // Agent 页面需要会员权限 — 等待会员信息加载后判断 - if (to.name === 'Agent') { - try { - // 先检查个人资料是否为空(强制简历上传机制) - if (ENABLE_FORCE_RESUME_UPLOAD) { - try { - const profileRes = await fetchProfile() - if (profileRes.code === '0' && !profileRes.data) { - next({ name: 'Login', query: { resumeUpload: '1' } }) - return + // Agent 页面需要额外的会员权限判断 + if (to.name === 'Agent') { + try { + if (!store.state.memberStatus) { + const memberRes = await fetchMemberStatus() + if (memberRes.code === '0' && memberRes.data) { + store.commit('SET_MEMBER_STATUS', memberRes.data) + } + } + const status = store.state.memberStatus + if (status && status.isMember && (status.memberType === 1 || status.memberType === 2)) { + next() + } else { + store.commit('SET_SHOW_MEMBER_ACCESS_DIALOG', true) + next(false) + } + } catch { + store.commit('SET_SHOW_MEMBER_ACCESS_DIALOG', true) + next(false) } - } catch { /* 接口异常不阻断 */ } - } - // 确保会员信息已加载到 store - if (!store.state.memberStatus) { - const res = await fetchMemberStatus() - if (res.code === '0' && res.data) { - store.commit('SET_MEMBER_STATUS', res.data) + return } - } - const status = store.state.memberStatus - // 试用会员(memberType=2)或正式会员(memberType=1)才能进入 - if (status && status.isMember && (status.memberType === 1 || status.memberType === 2)) { + next() } else { - // 免费用户 — 弹出会员权限拦截弹窗,阻止跳转 - store.commit('SET_SHOW_MEMBER_ACCESS_DIALOG', true) + // 未登录或 Cookie 失效 — 弹出登录弹窗,阻止导航 + store.commit('SET_AUTHENTICATED', false) + store.dispatch('openLogin', to.fullPath) next(false) } } catch { - // 接口异常时弹窗提示,不放行 - store.commit('SET_SHOW_MEMBER_ACCESS_DIALOG', true) + // 请求异常(网络错误等),也视为未登录 — 弹登录弹窗 + store.commit('SET_AUTHENTICATED', false) + store.dispatch('openLogin', to.fullPath) next(false) } return } - // 非鉴权路由 — 静默同步登录状态(节流:5 分钟内不重复请求) + // 白名单页面 — 静默同步登录状态(节流:5 分钟内不重复请求) if (Date.now() - lastCheckTime > CHECK_INTERVAL) { checkLogin() .then((res) => { @@ -201,25 +193,6 @@ router.beforeEach(async (to, _from, next) => { }) } - // 已登录 + 非首页/登录页/移动首页:检查个人资料是否为空,为空则强制跳转简历上传 - const skipProfileCheck = ['Home', 'HomeMobile', 'Login'] - if ( - ENABLE_FORCE_RESUME_UPLOAD && - store.state.isAuthenticated && - !skipProfileCheck.includes(to.name as string) - ) { - try { - const profileRes = await fetchProfile() - if (profileRes.code === '0' && !profileRes.data) { - // 个人资料为空 — 跳转到登录页简历上传步骤 - next({ name: 'Login', query: { resumeUpload: '1' } }) - return - } - } catch { - // 接口异常不阻断导航 - } - } - next() }) diff --git a/src/utils/request.ts b/src/utils/request.ts index a70196c..7b5b2de 100644 --- a/src/utils/request.ts +++ b/src/utils/request.ts @@ -40,10 +40,14 @@ service.interceptors.response.use( (error) => { const status = error.response?.status if (status === 401) { - // 同步重置前端登录状态,跳转登录页 + // 同步重置前端登录状态,弹出登录弹窗(白名单页面不弹) store.commit('SET_AUTHENTICATED', false) - router.push({ name: 'Login', query: { redirect: window.location.pathname } }) - ElMessage.error('登录已过期,请重新登录') + const publicPages = ['Home', 'HomeMobile', 'Login', 'SuperRjtfoewjksfewjLogin', 'Jobs', 'JobDetail', 'Announcement', 'AnnouncementDetail', 'MentorDetail', 'PrivacyPolicy'] + const currentRouteName = router.currentRoute.value.name as string + if (!publicPages.includes(currentRouteName)) { + store.dispatch('openLogin', window.location.pathname) + ElMessage.error('登录已过期,请重新登录') + } } else { ElMessage.error(error.response?.data?.msg || '请求失败') } diff --git a/src/views/Announcement.vue b/src/views/Announcement.vue new file mode 100644 index 0000000..e4d1ae6 --- /dev/null +++ b/src/views/Announcement.vue @@ -0,0 +1,996 @@ + + + diff --git a/src/views/AnnouncementDetail.vue b/src/views/AnnouncementDetail.vue new file mode 100644 index 0000000..9ea3c81 --- /dev/null +++ b/src/views/AnnouncementDetail.vue @@ -0,0 +1,506 @@ + + + diff --git a/src/views/Home.vue b/src/views/Home.vue index 04d5a4e..f7f2b23 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -688,9 +688,9 @@ onMounted(() => { store.dispatch('loadCommonData') }) -/** 点击登录按钮 — 跳转到登录页面,登录成功后跳转到 jobs 页面 */ +/** 点击登录按钮 — 打开登录弹窗,登录成功后跳转到 jobs 页面 */ function handleLoginClick() { - router.push({ name: 'Login', query: { redirect: '/jobs' } }) + store.dispatch('openLogin', '/jobs') } /** 隐私协议弹窗显示状态 */ diff --git a/src/views/Jobs.vue b/src/views/Jobs.vue index b0ffb5a..0885f8e 100644 --- a/src/views/Jobs.vue +++ b/src/views/Jobs.vue @@ -12,9 +12,11 @@
-
+ +
+
-
+
行业
@@ -60,7 +62,7 @@
-
+
岗位
@@ -106,7 +108,7 @@
-
+
地点
@@ -181,8 +183,8 @@ class="jobs-page__filter-dropdown-item" v-for="option in educationOptions" :key="option.value" - :class="{ 'jobs-page__filter-dropdown-item--active': selectedEducations.includes(option.value) }" - @click.stop="toggleEducation(option.value)" + :class="{ 'jobs-page__filter-dropdown-item--active': selectedEducation === option.value }" + @click.stop="selectEducation(option)" >{{ option.label }}
@@ -220,36 +222,60 @@
- -
- -
- -
- - 当前筛选共:{{ filterJobCount }} 个岗位,试试开启深度匹配,配寻找更适合你的岗位吧~ - - -
清空筛选条件
+ +
+ + 当前筛选共:{{ filterJobCount }} 个岗位,试试开启深度匹配,配寻找更适合你的岗位吧~ + + +
+ + +
+ +
- @@ -331,6 +357,12 @@
+ +
@@ -415,6 +447,7 @@ import { JOB_TYPE_OPTIONS, formatEmploymentType, formatEducation } from '@/store import SideNav from '@/components/SideNav.vue' import AiChat from '@/components/AiChat.vue' import JobPageHeader from '@/components/JobPageHeader.vue' +import JobStatsOverview from '@/components/JobStatsOverview.vue' import JobDislikeDialog from '@/components/JobDislikeDialog.vue' import JobFeedbackDialog from '@/components/JobFeedbackDialog.vue' import MemberDialog from '@/components/MemberDialog.vue' @@ -424,9 +457,10 @@ import JobCategorySelector from '@/components/tools/JobCategorySelector.vue' import RegionSelector from '@/components/tools/RegionSelector.vue' -import { fetchJobList, fetchJobListCount, fetchJobListMatch, fetchFavoriteList, toggleJobFavorite, removeJobFavorite, fetchFavoriteCount, fetchApplyList, fetchApplyCount, removeJobFromList, fetchCustomizeResume, fetchHotIndustries, fetchHotCategories, fetchHotCities } from '@/api/jobs' +import { fetchJobList, fetchJobListCount, fetchJobListMatch, fetchFavoriteList, toggleJobFavorite, removeJobFavorite, fetchFavoriteCount, fetchApplyList, fetchApplyCount, removeJobFromList, fetchCustomizeResume, fetchHotIndustries, fetchHotCategories, fetchHotCities, fetchJobCount } from '@/api/jobs' import type { JobListItem, JobListParams, FavoriteListParams, ApplyListParams, ApplyCountData, HotIndustryItem, HotCategoryItem, HotCityItem } from '@/api/jobs' import { getTimeEvent, setTimeEvent, timestampToLocalDateTime } from '@/utils/time' +import { getDailyJobCount } from '@/utils/dailyJobCount' import { fetchIndustryTree, fetchJobCategoryTree } from '@/api/common' import type { IndustryItem, JobCategoryItem } from '@/api/common' import { ElMessage } from 'element-plus' @@ -479,9 +513,63 @@ const activeTab = ref((route.query.tab as string) || 'recommend') /** 是否为推荐列表 Tab */ const isRecommendTab = computed(() => activeTab.value === 'recommend') +// ==================== 岗位概览统计数据 ==================== + +/** 筛选面板 DOM 引用 */ +const filtersPanelRef = ref(null) + +/** 筛选面板高度(同步给 JobStatsOverview 卡片) */ +const filtersPanelHeight = ref('auto') + +/** 监听筛选面板高度变化的 ResizeObserver */ +let filtersPanelObserver: ResizeObserver | null = null + +/** 今日新增岗位数量 */ +const statsDailyJobCount = getDailyJobCount() + +/** 全网岗位总数 */ +const statsJobCount = ref(0) + +/** 格式化岗位总数(万为单位) */ +const statsFormattedJobCount = computed(() => { + if (statsJobCount.value >= 10000) { + return Math.floor(statsJobCount.value / 10000) + 'w' + } + return statsJobCount.value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') +}) + +/** 当前日期(MM.DD 格式) */ +const statsCurrentDate = computed(() => { + const now = new Date() + const month = String(now.getMonth() + 1).padStart(2, '0') + const day = String(now.getDate()).padStart(2, '0') + return `${month}.${day}` +}) + +/** 加载岗位总数 */ +async function loadStatsJobCount() { + try { + const res = await fetchJobCount() + if (res.code === '0' && res.data !== undefined) { + statsJobCount.value = Number(res.data) + } + } catch (e) { + console.error('加载岗位总数失败', e) + } +} + /** 搜索框输入内容 */ const keyword = ref('') +/** 热门搜索标签列表 */ +const hotSearchTags = ref(['系统集成', 'AI产品经理', '广告投放', 'AI开发', '大模型']) + +/** 点击热门搜索标签 — 设置关键词并触发搜索 */ +function handleHotSearch(tag: string) { + keyword.value = tag + reloadFirstPage() +} + /** 当前选中的职位卡片索引 */ const selectedIndex = ref(0) @@ -730,30 +818,19 @@ const educationOptions = [ /** 学历下拉菜单显示状态 */ const showEducationDropdown = ref(false) -/** 已选学历列表 */ -const selectedEducations = ref([]) +/** 已选学历(单选,0表示不限/未选) */ +const selectedEducation = ref(0) /** 学历筛选显示文字 */ const educationFilterLabel = computed(() => { - if (!selectedEducations.value.length) return '学历要求' - return selectedEducations.value.map(v => educationOptions.find(o => o.value === v)?.label || '').join('、') + if (!selectedEducation.value) return '学历要求' + return educationOptions.find(o => o.value === selectedEducation.value)?.label || '学历要求' }) -/** 切换学历选中 */ -function toggleEducation(value: number) { - if (value === 0) { - // 选"不限"清空所有 - selectedEducations.value = [] - showEducationDropdown.value = false - reloadFirstPage() - return - } - const idx = selectedEducations.value.indexOf(value) - if (idx >= 0) { - selectedEducations.value.splice(idx, 1) - } else { - selectedEducations.value.push(value) - } +/** 选择学历(单选) */ +function selectEducation(option: { label: string; value: number }) { + selectedEducation.value = option.value === selectedEducation.value ? 0 : option.value + showEducationDropdown.value = false reloadFirstPage() } @@ -1241,6 +1318,22 @@ const showMemberDialog = ref(false) onMounted(async () => { document.addEventListener('click', closeDropdownOnClickOutside) + // 加载岗位总数 + loadStatsJobCount() + + // 监听筛选面板高度变化,同步给 JobStatsOverview + nextTick(() => { + if (filtersPanelRef.value) { + filtersPanelHeight.value = filtersPanelRef.value.offsetHeight + 'px' + filtersPanelObserver = new ResizeObserver((entries) => { + for (const entry of entries) { + filtersPanelHeight.value = entry.target.clientHeight + 'px' + } + }) + filtersPanelObserver.observe(filtersPanelRef.value) + } + }) + // 从缓存恢复公司类型筛选按钮的显示文字 // 需要等 userInfo 加载后再读取(刷新时 userInfo 可能还没有) const restoreCompanyTypeFilter = () => { @@ -1346,6 +1439,11 @@ onMounted(async () => { onBeforeUnmount(() => { document.removeEventListener('click', closeDropdownOnClickOutside) window.removeEventListener('resize', updateAiTipArrowPosition) + // 清理筛选面板高度监听 + if (filtersPanelObserver) { + filtersPanelObserver.disconnect() + filtersPanelObserver = null + } // 清理网格列数监听 if (gridResizeObserver) { gridResizeObserver.disconnect() @@ -1496,8 +1594,8 @@ function buildParams(): JobListParams { params.companyTypes = selectedCompanyTypes.value } // 学历要求筛选 - if (selectedEducations.value.length) { - params.educations = selectedEducations.value + if (selectedEducation.value) { + params.educations = [selectedEducation.value] } // 发布时间筛选(转为ISO 8601字符串) if (publishTimeRange.value) { @@ -1566,6 +1664,8 @@ async function loadNextPage() { function onListScroll() { const el = jobListRef.value if (!el) return + // 未登录时只展示一页,不加载更多 + if (!isAuthenticated.value) return // 距离底部小于 100px 时触发加载 const threshold = 100 if (el.scrollHeight - el.scrollTop - el.clientHeight < threshold) { @@ -1597,7 +1697,7 @@ const hasActiveFilters = computed(() => { || selectedRegionCodes.value.length > 0 || selectedEmploymentType.value !== null || selectedCompanyTypes.value.length > 0 - || selectedEducations.value.length > 0 + || selectedEducation.value > 0 || publishTimeRange.value !== null || keyword.value.trim() !== '' }) @@ -1665,7 +1765,7 @@ function clearAllFilters() { selectedCompanyTypes.value = [] saveCachedCompanyTypes([]) // 清空学历和发布时间 - selectedEducations.value = [] + selectedEducation.value = 0 publishTimeRange.value = null // 清空筛选按钮的显示文字 filters.value.forEach(f => { f.selected = '' }) @@ -1952,10 +2052,6 @@ function matchLevelText(score: number) { /** 跳转到岗位详情页 — 未登录弹登录框,登录后再跳转 */ function goToDetail(job: JobItem) { - if (!isAuthenticated.value) { - store.dispatch('openLogin', `/jobs/${job.id}`) - return - } // 保存当前列表状态到 store,返回时恢复 store.commit('SET_JOB_LIST_CACHE', { tab: activeTab.value,