简历列表,首页其他4个页面

This commit is contained in:
2026-08-13 11:34:51 +08:00
parent 351190a6d8
commit 0631cce83d
41 changed files with 4103 additions and 999 deletions
+5
View File
@@ -38,6 +38,11 @@ declare module 'vue' {
ElTooltip: typeof import('element-plus/es')['ElTooltip']
FullscreenLoading: typeof import('./src/components/FullscreenLoading.vue')['default']
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
HomeAbout: typeof import('./src/components/HomeAbout.vue')['default']
HomeAnnouncement: typeof import('./src/components/HomeAnnouncement.vue')['default']
HomeContent: typeof import('./src/components/HomeContent.vue')['default']
HomeJobRadar: typeof import('./src/components/HomeJobRadar.vue')['default']
HomeMentor: typeof import('./src/components/HomeMentor.vue')['default']
IndustrySelector: typeof import('./src/components/tools/IndustrySelector.vue')['default']
JobApplyMethodDialog: typeof import('./src/components/JobApplyMethodDialog.vue')['default']
JobCategorySelector: typeof import('./src/components/tools/JobCategorySelector.vue')['default']
+113
View File
@@ -91,6 +91,60 @@ export interface AnnouncementPageData {
list: AnnouncementItem[]
}
/** 校招公告统计数据 */
export interface AnnouncementStatistics {
/** 今日新增公告数 */
dayCount: number
/** 本月新增公告数 */
monthCount: number
/** 累计公告数 */
totalCount: number
}
/**
* 获取公告最新更新时间
* GET /public/recruitAnnouncement/lastUpdateTime
* 返回毫秒时间戳(如 1786387561000
*/
export function fetchAnnouncementLastUpdateTime() {
return request.get<any, ApiResult<number>>('/public/recruitAnnouncement/lastUpdateTime')
}
/**
* 获取公告毕业年份标签列表
* GET /public/recruitAnnouncement/year
* 返回年份数组,如 [2021, 2022, ..., 2029]
*/
export function fetchAnnouncementRecruitYearTags() {
return request.get<any, ApiResult<number[]>>('/public/recruitAnnouncement/year')
}
/**
* 获取公告学历筛选选项
* GET /public/recruitAnnouncement/education
* 返回学历字符串数组,如 ["专科","中专","博士","大专","本科","硕士","高中"]
*/
export function fetchAnnouncementEducationOptions() {
return request.get<any, ApiResult<string[]>>('/public/recruitAnnouncement/education')
}
/**
* 获取公告招聘批次筛选选项
* GET /public/recruitAnnouncement/batch
* 返回批次字符串数组
*/
export function fetchAnnouncementBatchOptions() {
return request.get<any, ApiResult<string[]>>('/public/recruitAnnouncement/batch')
}
/**
* 获取校招公告统计数据
* GET /public/recruitAnnouncement/statistics
*/
export function fetchAnnouncementStatistics() {
return request.get<any, ApiResult<AnnouncementStatistics>>('/public/recruitAnnouncement/statistics')
}
/**
* 获取校招公告分页列表
* POST /public/recruitAnnouncement/page
@@ -121,6 +175,8 @@ export interface AnnouncementDetail {
title: string
/** 公司简介 */
companyIntro: string
/** 公司规模 */
companyScale: string
/** 面向对象 */
targetAudience: string
/** 专业要求 */
@@ -141,6 +197,10 @@ export interface AnnouncementDetail {
inviteCode: string
/** 信息来源说明 */
source: string
/** 公告地址 */
announcementUrl: string
/** 投递地址 */
applyUrl: string
/** 发布时间 */
publishTime: InstantTime | null
/** 清洗状态 */
@@ -176,3 +236,56 @@ export function fetchAnnouncementDetail(id: string) {
params: { id },
})
}
// ==================== 校招公告推荐列表接口 ====================
/** 校招公告推荐请求参数 */
export interface AnnouncementRecommendParams {
/** 当前页码,从1开始 */
pageNum?: number
/** 每页条数 */
pageSize?: number
/** 关键字 */
keyword?: string
/** 公告ID(必传,字符串避免精度丢失) */
announcementId: string
}
/** 校招公告推荐列表项 */
export interface AnnouncementRecommendItem {
/** 公告ID */
id: number
/** 公司名称 */
companyName: string
/** 公司logo */
logoUrl: string
/** 岗位大类列表 */
categoryNames: string[]
/** 热招城市列表 */
cityNames: string[]
}
/** 校招公告推荐分页数据 */
export interface AnnouncementRecommendPageData {
/** 当前页码 */
pageNum: number
/** 每页条数 */
pageSize: number
/** 总记录数 */
total: number
/** 数据列表 */
list: AnnouncementRecommendItem[]
}
/**
* 获取校招公告推荐列表
* POST /public/recruitAnnouncement/recommend
*/
export function fetchAnnouncementRecommend(params: AnnouncementRecommendParams) {
return request.post<any, ApiResult<AnnouncementRecommendPageData>>('/public/recruitAnnouncement/recommend', {
pageNum: params.pageNum ?? 1,
pageSize: params.pageSize ?? 100,
...params,
})
}
+3 -3
View File
@@ -146,7 +146,7 @@ export interface HotCityItem {
* GET /job/industry/hot
*/
export function fetchHotIndustries() {
return request.get<any, ApiResult<HotIndustryItem[]>>('/job/industry/hot')
return request.get<any, ApiResult<HotIndustryItem[]>>('/job/public/industry/hot')
}
/**
@@ -154,7 +154,7 @@ export function fetchHotIndustries() {
* GET /job/category/hot
*/
export function fetchHotCategories() {
return request.get<any, ApiResult<HotCategoryItem[]>>('/job/category/hot')
return request.get<any, ApiResult<HotCategoryItem[]>>('/job/public/category/hot')
}
/**
@@ -162,7 +162,7 @@ export function fetchHotCategories() {
* GET /job/city/hot
*/
export function fetchHotCities() {
return request.get<any, ApiResult<HotCityItem[]>>('/job/city/hot')
return request.get<any, ApiResult<HotCityItem[]>>('/job/public/city/hot')
}
// ==================== 求职意向 ====================
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 167 KiB

@@ -176,6 +176,7 @@
gap: 0.24rem;
margin-bottom: 0.16rem;
margin-left: 0.4rem;
flex-wrap: wrap;
}
.agent-browser-guide-drawer__browser-link {
+1 -3
View File
@@ -5,12 +5,10 @@
width: var(--chat-width, 3.6rem);
height: var(--app-height, 100vh);
background: $bg-white;
position: fixed;
right: 0;
top: 0;
display: flex;
flex-direction: column;
box-sizing: border-box;
flex-shrink: 0;
// ==================== 顶部头部区域 ====================
&__header {
@@ -0,0 +1,484 @@
@use '../variables' as *;
// ========================================
// 首页-关于我们Tab样式
// ========================================
.home-about {
font-size: 0.14rem;
width: 100%;
// ==================== Hero 区域 ====================
&__hero {
position: relative;
padding: 0.6rem 0 0.8rem;
overflow: hidden;
}
// 底部居中渐变浅色色块
&__hero-orb {
position: absolute;
bottom: -2.5rem;
left: 50%;
transform: translateX(-50%);
width: 8rem;
height: 3rem;
border-radius: 50%;
background: rgba(82, 202, 209, 0.15);
filter: blur(1rem);
pointer-events: none;
}
&__hero-inner {
position: relative;
z-index: 1;
text-align: center;
max-width: 12rem;
margin: 0 auto;
}
&__hero-kicker {
font-size: 0.20rem;
line-height: 0.27rem;
font-weight: 450;
color: $accent;
margin-bottom: 0.24rem;
}
&__hero-title {
font-size: 0.54rem;
line-height: 0.72rem;
font-weight: 630;
color: #111111;
margin-top: 0.24rem;
margin-bottom: 0.24rem;
}
&__hero-subtitle {
font-size: 0.20rem;
line-height: 0.27rem;
font-weight: 330;
color: #64748B;
margin-bottom: 0.40rem;
}
&__hero-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 1.84rem;
height: 0.56rem;
background: $accent;
border-radius: 0.16rem;
border: none;
font-size: 0.16rem;
line-height: 0.21rem;
font-weight: 520;
color: #FFFFFF;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: $accent-hover;
}
}
// ==================== 通用 kicker ====================
&__kicker {
display: block;
font-size: 0.14rem;
line-height: 0.19rem;
font-weight: 520;
color: $accent;
margin-bottom: 0.12rem;
}
// ==================== 通用标题 ====================
&__section-title {
font-size: 0.48rem;
line-height: 0.64rem;
font-weight: 520;
color: #111111;
text-align: center;
}
&__section-subtitle {
font-size: 0.20rem;
line-height: 0.27rem;
font-weight: 330;
color: #64748B;
text-align: center;
margin-top: 0.16rem;
}
// ==================== 产品介绍 ====================
&__intro {
padding: 0.80rem 0;
background: $bg-white;
}
&__intro-inner {
max-width: 12rem;
margin: 0 auto;
display: flex;
align-items: flex-start;
gap: 0.70rem;
}
&__intro-img {
flex-shrink: 0;
width: 5.06rem;
height: 3.68rem;
background: #F5FAFA;
border-radius: 0.22rem;
display: flex;
align-items: center;
justify-content: center;
}
&__intro-img-placeholder {
width: 100%;
height: 100%;
}
&__intro-text {
flex: 1;
}
&__intro-desc {
font-size: 0.16rem;
line-height: 0.24rem;
font-weight: 380;
color: #64748B;
margin-top: 0.16rem;
}
// ==================== 功能亮点 ====================
&__features {
position: relative;
padding: 0.80rem 0;
overflow: hidden;
}
&__features-header {
text-align: center;
margin-bottom: 0.60rem;
}
&__features-grid {
max-width: 12rem;
margin: 0 auto;
display: flex;
gap: 0.16rem;
justify-content: center;
}
&__feature-card {
width: 2.68rem;
height: 2.97rem;
background: $bg-white;
box-shadow: 0px 6px 20px rgba(0, 0, 0, 0.06);
border-radius: 0.16rem;
padding: 0.24rem 0.24rem;
box-sizing: border-box;
}
&__feature-icon {
width: 0.58rem;
height: 0.58rem;
background: #F4FBFB;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 0.16rem;
}
&__feature-title {
font-size: 0.18rem;
line-height: 0.24rem;
font-weight: 520;
color: #111111;
margin-bottom: 0.08rem;
}
&__feature-desc {
font-size: 0.14rem;
line-height: 0.24rem;
font-weight: 330;
color: $text-middle;
}
// ==================== 为什么创立 ====================
&__why {
padding: 0.80rem 0;
max-width: 12rem;
margin: 0 auto;
}
&__why-header {
text-align: center;
margin-bottom: 0.60rem;
}
&__why-content {
background: $bg-white;
box-shadow: 0px 6px 20px rgba(0, 0, 0, 0.06);
border-radius: 0.32rem;
padding: 0.40rem;
overflow: hidden;
}
&__why-block {
display: flex;
align-items: flex-start;
gap: 0.40rem;
margin-bottom: 0.40rem;
&:last-child {
margin-bottom: 0;
}
&--reverse {
}
}
&__why-text {
flex: 1;
h3 {
font-size: 0.24rem;
line-height: 0.32rem;
font-weight: 520;
color: #111111;
margin-bottom: 0.16rem;
}
p {
font-size: 0.16rem;
line-height: 0.30rem;
font-weight: 330;
color: $text-middle;
margin-bottom: 0.12rem;
}
}
&__why-sign {
font-size: 0.16rem;
line-height: 0.30rem;
font-weight: 330;
color: $text-middle;
text-align: right;
}
&__why-img {
flex-shrink: 0;
width: 5.44rem;
height: 2.59rem;
background: #F5FAFA;
border-radius: 0.16rem;
}
// ==================== 万千毕业生信赖 ====================
&__trust {
padding: 0.80rem 0;
max-width: 12rem;
margin: 0 auto;
}
&__trust-header {
text-align: center;
margin-bottom: 0.60rem;
}
&__trust-dark-card {
background: #111111;
border-radius: 0.16rem;
padding: 0.28rem 0.40rem;
margin-bottom: 0.24rem;
position: relative;
overflow: hidden;
}
&__trust-dark-text {
max-width: 75%;
p {
font-size: 0.16rem;
line-height: 0.30rem;
font-weight: 330;
color: #FFFFFF;
margin-bottom: 0.10rem;
&:first-child {
font-weight: 520;
margin-bottom: 0.16rem;
}
}
}
&__trust-dark-logo {
position: absolute;
right: 0.40rem;
top: 50%;
transform: translateY(-50%);
width: 1.64rem;
height: 1.64rem;
opacity: 0.1;
background: $bg-white;
border-radius: 50%;
}
&__trust-cards {
display: flex;
gap: 0.24rem;
margin-bottom: 0.24rem;
}
&__trust-card {
flex: 1;
background: $bg-white;
box-shadow: 0px 20px 50px rgba(0, 0, 0, 0.05);
border-radius: 0.16rem;
padding: 0.24rem 0.28rem;
h4 {
font-size: 0.18rem;
line-height: 0.24rem;
font-weight: 520;
color: #111111;
margin-bottom: 0.16rem;
}
p {
font-size: 0.14rem;
line-height: 0.24rem;
font-weight: 330;
color: $text-middle;
}
}
&__trust-summary {
font-size: 0.24rem;
line-height: 0.32rem;
font-weight: 520;
color: $text-dark;
text-align: center;
margin-top: 0.40rem;
}
// ==================== 价值观 ====================
&__values {
padding: 0.80rem 0;
max-width: 12rem;
margin: 0 auto;
}
&__values-header {
text-align: center;
margin-bottom: 0.60rem;
}
&__values-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.24rem;
}
&__value-card {
background: #F9FAFB;
border-radius: 0.24rem;
padding: 0.32rem;
}
&__value-icon {
margin-bottom: 0.16rem;
}
&__value-title {
font-size: 0.24rem;
line-height: 0.32rem;
font-weight: 630;
color: #111111;
margin-bottom: 0.08rem;
}
&__value-desc {
font-size: 0.18rem;
line-height: 0.24rem;
font-weight: 330;
color: $text-middle;
}
// ==================== 联系我们 ====================
&__contact {
padding: 0.80rem 0;
max-width: 12rem;
margin: 0 auto;
}
&__contact-header {
text-align: center;
margin-bottom: 0.60rem;
}
&__contact-grid {
display: flex;
gap: 0.24rem;
justify-content: center;
}
&__contact-card {
flex: 1;
max-width: 3.77rem;
background: $bg-white;
box-shadow: 0px 6px 20px rgba(0, 0, 0, 0.06);
border-radius: 0.16rem;
padding: 0.24rem;
display: flex;
align-items: flex-start;
gap: 0.16rem;
}
&__contact-icon {
flex-shrink: 0;
width: 0.58rem;
height: 0.58rem;
background: #F4FBFB;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
&__contact-info {
h4 {
font-size: 0.18rem;
line-height: 0.24rem;
font-weight: 520;
color: #111111;
margin-bottom: 0.04rem;
}
p {
font-size: 0.14rem;
line-height: 0.24rem;
font-weight: 330;
color: $text-middle;
margin-bottom: 0.08rem;
}
}
&__contact-link {
font-size: 0.14rem;
line-height: 0.24rem;
font-weight: 520;
color: $accent;
text-decoration: none;
&:hover {
color: $accent-hover;
}
}
}
@@ -0,0 +1,387 @@
@use '../variables' as *;
// ========================================
// 首页-校招公告Tab样式
// ========================================
.home-announcement {
font-size: 0.14rem;
width: 100%;
// 第一部分容器
&__section {
width: 12rem;
margin: 0 auto;
padding: 0.6rem 0;
}
// 标题区域
&__header {
display: flex;
justify-content: space-between;
margin-bottom: 0.4rem;
}
&__title {
font-size: 0.36rem;
line-height: 0.48rem;
font-weight: 700;
color: $text-dark;
margin-bottom: 0.12rem;
}
&__subtitle {
font-size: 0.14rem;
line-height: 0.2rem;
color: $text-light;
}
// ==================== 筛选面板(与岗位雷达一致) ====================
&__filters {
background: $bg-white;
border: 1px solid #F0F0F0;
box-shadow: 0px 2px 6px rgba(15, 23, 42, 0.025);
border-radius: 0.14rem;
padding: 0.24rem 0.40rem;
margin-bottom: 0.32rem;
}
&__filter-row {
display: flex;
align-items: center;
margin-bottom: 0.16rem;
&:last-child {
margin-bottom: 0;
}
}
&__filter-label {
width: 0.28rem;
font-size: 0.14rem;
line-height: 0.19rem;
font-weight: 450;
color: $text-light;
flex-shrink: 0;
margin-right: 0.24rem;
}
&__filter-chips {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.14rem;
}
&__chip {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.06rem 0.12rem;
border-radius: 999px;
background: #F5F8F8;
font-size: 0.14rem;
line-height: 0.19rem;
font-weight: 330;
color: $text-middle;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
&:hover {
background: #E8F7F8;
}
&--active {
background: $accent;
color: #FFFFFF;
font-weight: 520;
&:hover {
background: $accent-hover;
}
}
}
// ==================== 公告卡片网格 ====================
&__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.16rem;
}
// 公告卡片
&__card {
background: $bg-white;
border: 1px solid #E8F1F1;
border-radius: 0.16rem;
cursor: pointer;
transition: box-shadow 0.2s, border-color 0.2s;
overflow: hidden;
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
border-color: #D0E8E8;
}
}
// 卡片主体
&__card-body {
display: flex;
padding: 0.16rem 0.20rem;
gap: 0.12rem;
}
// 左侧logo容器
&__card-logo-wrap {
flex-shrink: 0;
}
&__card-logo {
width: 0.48rem;
height: 0.48rem;
border-radius: 0.08rem;
border: 1px solid #F4F4F4;
object-fit: cover;
&--default {
display: flex;
align-items: center;
justify-content: center;
background: $bg-white;
}
}
// 右侧内容区
&__card-content {
flex: 1;
min-width: 0;
}
// 标题行
&__card-title-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.06rem;
}
&__card-title {
font-size: 0.16rem;
line-height: 0.21rem;
font-weight: 520;
color: $text-dark;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
min-width: 0;
}
// 公司类型标签(蓝色)
&__card-type-tag {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.02rem 0.08rem;
background: #ECF3FC;
border-radius: 0.04rem;
font-size: 0.12rem;
line-height: 0.16rem;
font-weight: 330;
color: #2672E1;
white-space: nowrap;
margin-left: 0.08rem;
flex-shrink: 0;
}
// 标签行
&__card-tags {
display: flex;
align-items: center;
gap: 0.08rem;
margin-bottom: 0.10rem;
}
&__card-tag {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.02rem 0.08rem;
border-radius: 0.04rem;
font-size: 0.12rem;
line-height: 0.16rem;
font-weight: 330;
// 批次标签(青色)
&--batch {
background: #F0FAFB;
color: $accent;
}
// 学历标签(橙色)
&--edu {
background: #FAF2ED;
color: #D17E52;
font-weight: 305;
}
}
// 信息行(岗位/城市)
&__card-info-line {
display: flex;
align-items: center;
gap: 0.04rem;
font-size: 0.14rem;
line-height: 0.19rem;
font-weight: 380;
color: #64748B;
margin-bottom: 0.04rem;
&:last-child {
margin-bottom: 0;
}
}
&__card-icon {
width: 0.16rem;
height: 0.16rem;
flex-shrink: 0;
}
// 卡片底部渐变条
&__card-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.10rem 0.20rem;
background: linear-gradient(270deg, rgba(159, 208, 81, 0.08) 0%, rgba(82, 202, 209, 0.08) 62.02%);
}
&__card-update {
font-size: 0.14rem;
line-height: 0.19rem;
font-weight: 330;
color: #94A3B8;
}
&__card-deadline {
font-size: 0.14rem;
line-height: 0.19rem;
font-weight: 520;
color: $text-dark;
}
// ==================== 加载中 ====================
&__loading {
display: flex;
align-items: center;
justify-content: center;
padding: 0.4rem 0;
gap: 0.08rem;
color: $text-light;
font-size: 0.14rem;
}
&__spinner {
width: 0.2rem;
height: 0.2rem;
border: 2px solid #e5e7eb;
border-top-color: $accent;
border-radius: 50%;
animation: home-announcement-spin 0.8s linear infinite;
}
// ==================== 查看更多按钮 ====================
&__more {
display: flex;
justify-content: center;
margin-top: 0.32rem;
}
&__more-btn {
padding: 0.10rem 0.40rem;
border-radius: 0.08rem;
background: $text-dark;
color: #FFFFFF;
font-size: 0.14rem;
line-height: 0.2rem;
font-weight: 500;
border: none;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: #333;
}
}
// ==================== 首页版统计卡片 ====================
&__stats-card {
display: flex;
align-items: center;
padding: 0.20rem 0.24rem;
gap: 0.40rem;
background: $bg-white;
border: 1px solid #F0F0F0;
box-shadow: 0px 2px 6px rgba(15, 23, 42, 0.025);
border-radius: 0.12rem;
}
&__stats-title-wrap {
display: flex;
flex-direction: column;
justify-content: center;
gap: 0.08rem;
}
&__stats-title {
font-size: 0.16rem;
line-height: 0.21rem;
font-weight: 520;
color: $text-dark;
}
&__stats-date {
font-size: 0.12rem;
line-height: 0.16rem;
font-weight: 330;
color: #64748B;
}
&__stats-data {
display: flex;
align-items: center;
gap: 0.20rem;
}
&__stats-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.04rem;
}
&__stats-num {
font-size: 0.30rem;
line-height: 0.40rem;
font-weight: 520;
color: $accent;
}
&__stats-label {
font-size: 0.14rem;
line-height: 0.19rem;
font-weight: 330;
color: $text-middle;
}
&__stats-divider {
width: 0;
height: 0.52rem;
border-left: 1px solid #F0F0F0;
}
}
@keyframes home-announcement-spin {
to { transform: rotate(360deg); }
}
@@ -0,0 +1,287 @@
@use '../variables' as *;
// ========================================
// 首页-岗位雷达Tab样式
// ========================================
.home-job-radar {
font-size: 0.14rem;
width: 100%;
// 第一部分容器
&__section {
width: 12rem;
margin: 0 auto;
padding: 0.6rem 0;
}
// 标题区域
&__header {
text-align: center;
margin-bottom: 0.4rem;
}
&__title {
font-size: 0.36rem;
line-height: 0.48rem;
font-weight: 700;
color: $text-dark;
margin-bottom: 0.12rem;
}
&__subtitle {
font-size: 0.14rem;
line-height: 0.2rem;
color: $text-light;
}
// ==================== 筛选面板 ====================
&__filters {
background: $bg-white;
border: 1px solid #F0F0F0;
box-shadow: 0px 2px 6px rgba(15, 23, 42, 0.025);
border-radius: 0.14rem;
padding: 0.24rem 0.40rem;
margin-bottom: 0.32rem;
}
// 筛选行
&__filter-row {
display: flex;
align-items: center;
margin-bottom: 0.16rem;
&:last-child {
margin-bottom: 0;
}
}
// 筛选标签文字
&__filter-label {
width: 0.28rem;
font-size: 0.14rem;
line-height: 0.19rem;
font-weight: 450;
color: $text-light;
flex-shrink: 0;
margin-right: 0.24rem;
}
// 筛选chip容器
&__filter-chips {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.14rem;
}
// 筛选chip
&__chip {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.06rem 0.12rem;
border-radius: 999px;
background: #F5F8F8;
font-size: 0.14rem;
line-height: 0.19rem;
font-weight: 330;
color: $text-middle;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
&:hover {
background: #E8F7F8;
}
// 选中态
&--active {
background: $accent;
color: #FFFFFF;
font-weight: 520;
&:hover {
background: $accent-hover;
}
}
}
// ==================== 岗位卡片网格 ====================
&__grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.16rem;
}
// 岗位卡片
&__card {
background: $bg-white;
border: 1px solid #F0F0F0;
border-radius: 0.08rem;
padding: 0.16rem;
cursor: pointer;
transition: box-shadow 0.2s, border-color 0.2s;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 1.33rem;
box-sizing: border-box;
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
border-color: #E0E0E0;
}
}
// 卡片上半部分
&__card-top {
// 无需额外样式
}
// 岗位标题
&__card-title {
font-size: 0.16rem;
line-height: 0.21rem;
font-weight: 520;
color: $text-dark;
margin-bottom: 0.08rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// 标签行
&__card-tags {
display: flex;
align-items: center;
gap: 0.08rem;
}
// 单个标签
&__card-tag {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.03rem 0.06rem;
background: #F3F4F6;
border-radius: 0.04rem;
font-size: 0.12rem;
line-height: 0.16rem;
font-weight: 380;
color: $text-middle;
}
// 卡片下半部分
&__card-bottom {
display: flex;
align-items: center;
justify-content: space-between;
}
// 公司信息
&__card-company {
display: flex;
align-items: center;
gap: 0.04rem;
}
// 公司logo
&__card-logo {
width: 0.24rem;
height: 0.24rem;
border-radius: 50%;
border: 1px solid #F4F4F4;
object-fit: cover;
flex-shrink: 0;
&--default {
display: flex;
align-items: center;
justify-content: center;
background: $bg-white;
}
}
// 公司名称
&__card-company-name {
font-size: 0.14rem;
line-height: 0.19rem;
font-weight: 450;
color: $text-dark;
max-width: 1.69rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// 公司类型标签
&__card-company-type {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.03rem 0.06rem;
background: #ECFDF3;
border-radius: 0.04rem;
font-size: 0.12rem;
line-height: 0.16rem;
font-weight: 380;
color: #067647;
}
// 发布时间
&__card-time {
font-size: 0.12rem;
line-height: 0.16rem;
font-weight: 330;
color: $text-light;
text-align: right;
}
// ==================== 加载中 ====================
&__loading {
display: flex;
align-items: center;
justify-content: center;
padding: 0.4rem 0;
gap: 0.08rem;
color: $text-light;
font-size: 0.14rem;
}
&__spinner {
width: 0.2rem;
height: 0.2rem;
border: 2px solid #e5e7eb;
border-top-color: $accent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
// ==================== 查看更多按钮 ====================
&__more {
display: flex;
justify-content: center;
margin-top: 0.32rem;
}
&__more-btn {
padding: 0.10rem 0.40rem;
border-radius: 0.08rem;
background: $text-dark;
color: #FFFFFF;
font-size: 0.14rem;
line-height: 0.2rem;
font-weight: 500;
border: none;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: #333;
}
}
}
@keyframes spin {
to { transform: rotate(360deg); }
}
@@ -0,0 +1,310 @@
@use '../variables' as *;
// ========================================
// 首页-求职辅导Tab样式
// ========================================
.home-mentor {
font-size: 0.14rem;
width: 100%;
// 第一部分容器
&__section {
width: 12rem;
margin: 0 auto;
padding: 0.6rem 0;
}
// 标题区域
&__header {
text-align: center;
margin-bottom: 0.4rem;
}
&__title {
font-size: 0.36rem;
line-height: 0.48rem;
font-weight: 700;
color: $text-dark;
margin-bottom: 0.12rem;
}
&__subtitle {
font-size: 0.14rem;
line-height: 0.2rem;
color: $text-middle;
}
// ==================== 行业分类Tab ====================
&__tabs {
display: flex;
align-items: center;
justify-content: center;
gap: 0.08rem;
margin-bottom: 0.32rem;
}
&__tab {
padding: 0.08rem 0.20rem;
border-radius: 999px;
font-size: 0.14rem;
line-height: 0.19rem;
font-weight: 400;
color: $text-dark;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
border: 1px solid transparent;
&:hover {
background: #F0FAFB;
color: $accent;
}
// 选中态
&--active {
background: $accent;
color: #FFFFFF;
font-weight: 500;
&:hover {
background: $accent-hover;
color: #FFFFFF;
}
}
}
// ==================== 导师列表 ====================
&__list-wrap {
height: 11rem;
overflow-y: auto;
}
&__list {
// 无需额外样式
}
&__no-more {
text-align: center;
padding: 0.24rem 0;
font-size: 0.14rem;
color: $text-light;
}
// 导师卡片
&__card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.24rem 0.32rem;
background: $bg-white;
border: 1px solid #F0F0F0;
border-radius: 0.12rem;
margin-bottom: 0.12rem;
cursor: pointer;
transition: box-shadow 0.2s, border-color 0.2s;
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
border-color: #E0E8E8;
}
&:last-child {
margin-bottom: 0;
}
}
// 卡片左侧 — 限制最大宽度让右侧话题纵向对齐
&__card-left {
display: flex;
align-items: center;
gap: 0.14rem;
flex: 1 1 0;
max-width: 50%;
min-width: 0;
}
// 头像
&__avatar-wrap {
flex-shrink: 0;
}
&__avatar {
width: 0.56rem;
height: 0.56rem;
border-radius: 50%;
object-fit: cover;
}
&__avatar-placeholder {
width: 0.56rem;
height: 0.56rem;
border-radius: 50%;
background: #EDF9FA;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.2rem;
font-weight: 600;
color: $accent;
}
// 导师信息
&__card-info {
flex: 1;
min-width: 0;
}
&__card-name-row {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.10rem;
margin-bottom: 0.08rem;
}
&__card-name {
font-size: 0.18rem;
line-height: 0.24rem;
font-weight: 600;
color: $text-dark;
}
&__card-meta {
display: flex;
align-items: center;
gap: 0.04rem;
font-size: 0.13rem;
line-height: 0.18rem;
font-weight: 400;
color: $text-middle;
}
&__card-agency-logo {
width: 0.18rem;
height: 0.18rem;
border-radius: 50%;
object-fit: cover;
}
// 标签列表
&__card-labels {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.06rem;
}
&__card-label {
display: inline-flex;
align-items: center;
padding: 0.02rem 0.08rem;
background: #F0FAFB;
border-radius: 0.04rem;
font-size: 0.12rem;
line-height: 0.16rem;
font-weight: 330;
color: $accent;
white-space: nowrap;
}
// 卡片右侧 — flex:1 让话题区自适应剩余空间
&__card-right {
flex: 1 1 0;
display: flex;
align-items: center;
gap: 0.12rem;
min-width: 0;
}
// 话题列表 — 占据右侧剩余空间,自动截断
&__card-topics {
flex: 1;
min-width: 0;
}
&__card-topic {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.12rem;
line-height: 1.6;
color: $text-middle;
}
// 查看详情按钮
&__detail-btn {
display: flex;
align-items: center;
gap: 0.04rem;
padding: 0;
border: none;
background: none;
font-size: 0.14rem;
line-height: 0.19rem;
font-weight: 450;
color: $accent;
cursor: pointer;
white-space: nowrap;
transition: color 0.2s;
&:hover {
color: $accent-hover;
}
}
// ==================== 加载中 ====================
&__loading {
display: flex;
align-items: center;
justify-content: center;
padding: 0.4rem 0;
gap: 0.08rem;
color: $text-light;
font-size: 0.14rem;
}
&__spinner {
width: 0.2rem;
height: 0.2rem;
border: 2px solid #e5e7eb;
border-top-color: $accent;
border-radius: 50%;
animation: home-mentor-spin 0.8s linear infinite;
}
// 空列表
&__empty {
text-align: center;
padding: 0.4rem 0;
font-size: 0.14rem;
color: $text-light;
}
// ==================== 查看更多按钮 ====================
&__more {
display: flex;
justify-content: center;
margin-top: 0.32rem;
}
&__more-btn {
padding: 0.10rem 0.40rem;
border-radius: 0.08rem;
background: $text-dark;
color: #FFFFFF;
font-size: 0.14rem;
line-height: 0.2rem;
font-weight: 500;
border: none;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: #333;
}
}
}
@keyframes home-mentor-spin {
to { transform: rotate(360deg); }
}
+1 -3
View File
@@ -9,10 +9,8 @@
color: #fff;
padding: 0.2rem 0.12rem;
box-sizing: border-box;
position: fixed;
left: 0;
top: 0;
z-index: 100;
flex-shrink: 0;
// 窄版模式(window.innerWidth < 1440
&--narrow {
+4
View File
@@ -49,6 +49,10 @@
@use './components/mentor-aside-panel.scss';
@use './components/agent-browser-install-guide.scss';
@use './components/login-dialog.scss';
@use './components/home-job-radar.scss';
@use './components/home-announcement.scss';
@use './components/home-mentor.scss';
@use './components/home-about.scss';
// 全局样式(优先级最高)
@use './auto.scss';
+1 -1
View File
@@ -6,9 +6,9 @@
min-height: var(--app-height, 100vh);
background: $bg-main;
font-size: 0.14rem;
min-width: 12.8rem;
// 主内容区域(左侧导航栏右边的部分)
&__content {
margin-left: var(--nav-width, 2.2rem);
flex: 1;
overflow-y: auto;
height: var(--app-height, 100vh);
@@ -11,7 +11,6 @@
// 主内容区(与 JobDetail 一致,通过 margin-left 避开侧边栏)
&__content {
margin-left: var(--nav-width, 2.2rem);
flex: 1;
height: var(--app-height, 100vh);
box-sizing: border-box;
@@ -473,6 +472,7 @@
color: $text-middle;
line-height: 0.16rem;
white-space: nowrap;
overflow: hidden;
}
// ==================== 校招推荐列表项 ====================
+3 -3
View File
@@ -3,9 +3,9 @@
.announcement-page {
display: flex;
min-width: 12.8rem;
&__content {
margin-left: var(--nav-width, 2.2rem);
flex: 1;
padding-bottom: 0.18rem;
height: var(--app-height, 100vh);
@@ -17,7 +17,7 @@
// 页面标题
&__title {
font-size: 0.24rem;
font-size: 0.18rem;
font-weight: 700;
color: $text-dark;
margin: 0;
@@ -26,7 +26,7 @@
}
&__subtitle {
font-size: 0.13rem;
font-size: 0.14rem;
color: $text-light;
padding: 0;
}
+46 -4
View File
@@ -9,13 +9,14 @@
.home-page {
width: 100%;
overflow-x: hidden;
background: #fff;
background: radial-gradient(63.8% 19.39% at 60.25% 0%, #CEF0F2 0%, #FFFFFF 100%) no-repeat top / 100% 5rem, #fff;
color: #111;
font-family: system-ui, -apple-system, sans-serif;
// 全局基础行高重置,防止 1rem=100px 导致子元素继承异常行高
line-height: 1.5;
font-size: 0.14rem;
// ==================== 顶部导航栏 ====================
.home-nav {
position: sticky;
@@ -33,6 +34,8 @@
display: flex;
align-items: center;
justify-content: space-between;
background: transparent;
transition: background 0.3s, box-shadow 0.3s;
&--scorlled {
position: fixed;
@@ -67,6 +70,47 @@
display: block;
}
// 中间Tab导航区域
&__tabs {
display: flex;
align-items: center;
gap: 0.40rem;
}
// 单个Tab项
&__tab {
position: relative;
font-size: 0.14rem;
line-height: 0.2rem;
font-weight: 400;
color: $text-dark;
cursor: pointer;
padding-bottom: 0.06rem;
transition: font-weight 0.2s;
&:hover {
color: $accent;
}
// 选中态:加粗 + 底部下划线
&--active {
font-weight: 600;
color: $text-dark;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 0.2rem;
height: 0.02rem;
background: $accent;
border-radius: 0.01rem;
}
}
}
// 导航右侧 CTA 按钮
&__btn {
padding: 0.1rem 0.32rem;
@@ -85,11 +129,9 @@
// ==================== Hero 主视觉区 ====================
.home-hero {
background: #fff;
position: relative;
overflow: hidden;
font-size: 0.14rem;
background: radial-gradient(34.95% 29.57% at 60.26041666666667% 0%, rgba(206, 240, 242, 1) 0%, rgba(255, 255, 255, 1) 100%);
// 背景色块 — 顶部
&__orb {
position: absolute;
@@ -107,7 +149,7 @@
height: 6rem;
border-radius: 50%;
filter: blur(0.5rem);
background: rgba(82, 202, 209, 0.24);
//background: rgba(82, 202, 209, 0.24);
opacity: 0;
}
+2 -2
View File
@@ -2,9 +2,9 @@
// ==================== 岗位详情页 ====================
.job-detail {
min-width: 12.8rem;
&__content {
margin-left: var(--nav-width, 2.2rem);
margin-right: var(--chat-width, 3.6rem);
flex: 1;
height: var(--app-height, 100vh);
box-sizing: border-box;
+6 -4
View File
@@ -4,10 +4,9 @@
.jobs-page {
display: flex;
min-width: 12.8rem;
&__content {
margin-left: var(--nav-width, 2.2rem);
margin-right: var(--chat-width, 3.6rem);
flex: 1;
padding-bottom: 0.18rem;
height: var(--app-height, 100vh);
@@ -15,6 +14,8 @@
overflow: hidden;
background: $bg-main;
font-size: 0.14rem;
display: flex;
flex-direction: column;
}
// 页面标题
@@ -601,11 +602,12 @@
}
}
// 职位列表(用 calc 减去上方头部+筛选栏大致高度,保证列表独立滚动)
// 职位列表(flex:1 自动占满剩余空间,保证列表独立滚动)
&__list {
gap: 0;
overflow-y: auto;
height: calc(var(--app-height, 100vh) - 1.8rem);
flex: 1;
min-height: 0;
padding-bottom: 0.2rem;
margin: 0 0.24rem;
}
+1
View File
@@ -1,6 +1,7 @@
@use '../variables' as *;
/* 登录页面 — 左右分栏全屏布局 */
.login-view {
min-width: 12.8rem;
font-size: 0.14rem;
display: flex;
align-items: flex-start;
+1 -1
View File
@@ -3,6 +3,7 @@
// ==================== 导师详情页样式 ====================
.mentor-detail {
min-width: 12.8rem;
font-size: 0.14rem;
width: 100%;
height: var(--app-height, 100vh);
@@ -14,7 +15,6 @@
flex: 1;
height: 100%;
overflow: hidden;
margin-left: var(--nav-width, 2.2rem);
display: flex;
}
+1 -1
View File
@@ -3,6 +3,7 @@
// ==================== Mentor 导师页面样式 ====================
.mentor-page {
min-width: 12.8rem;
font-size: 0.14rem;
width: 100%;
height: var(--app-height, 100vh);
@@ -14,7 +15,6 @@
flex: 1;
height: 100%;
overflow: hidden;
margin-left: var(--nav-width, 2.2rem);
}
// 左侧主内容区
+2 -1
View File
@@ -2,8 +2,9 @@
// ==================== 个人资料页 ====================
.profile-page {
min-width: 12.8rem;
&__content {
margin-left: var(--nav-width, 2.2rem);
flex: 1;
padding-bottom: 0.12rem;
height: var(--app-height, 100vh);
+2 -1
View File
@@ -2,8 +2,9 @@
// ==================== 简历详情页 ====================
.resume-detail {
min-width: 12.8rem;
&__content {
margin-left: var(--nav-width, 2.2rem);
flex: 1;
padding-bottom: 0.24rem;
height: var(--app-height, 100vh);
+253 -208
View File
@@ -1,9 +1,10 @@
@use '../variables' as *;
// ==================== 简历列表页 ====================
// ==================== 简历列表页卡片视图 ====================
.resume-page {
min-width: 12.8rem;
&__content {
margin-left: var(--nav-width, 2.2rem);
flex: 1;
height: var(--app-height, 100vh);
box-sizing: border-box;
@@ -29,246 +30,278 @@
margin: 0;
}
&__upload-btn {
display: flex;
align-items: center;
gap: 0.06rem;
background: $btn-dark;
color: $bg-white;
border: none;
border-radius: 0.08rem;
padding: 0.08rem 0.14rem;
font-size: 0.13rem;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: $btn-dark-hover;
}
}
// 从个人资料创建按钮描边样式
&__create-btn {
display: flex;
align-items: center;
gap: 0.06rem;
background: $bg-white;
color: $accent;
border: 1px solid $accent;
border-radius: 0.08rem;
padding: 0.08rem 0.14rem;
font-size: 0.13rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
&:hover {
background: rgba($accent, 0.06);
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
&__upload-icon {
width: 0.14rem;
height: 0.14rem;
}
// 表格容器超出时可滚动
&__table-wrap {
background: $bg-white;
border-radius: 0.12rem;
margin: 0.18rem 0.17rem;
// 卡片网格容器
&__card-grid {
flex: 1;
overflow-y: auto;
flex: 0 1 auto;
min-height: 0;
padding: 0.24rem;
display: flex;
flex-wrap: wrap;
align-content: flex-start;
gap: 0.24rem;
}
&__table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
}
&__th {
text-align: left;
padding: 0.14rem 0.2rem;
font-size: 0.12rem;
font-weight: 600;
color: $text-dark;
border-bottom: 1px solid $border-color;
white-space: nowrap;
width: 15%;
position: sticky;
top: 0;
background: $bg-white;
z-index: 1;
&--action {
width: 25%;
}
}
&__row {
// ==================== 简历卡片 ====================
&__card {
width: 2.10rem;
height: 2.97rem;
border-radius: 0.12rem;
cursor: pointer;
transition: background 0.15s;
transition: transform 0.3s ease, filter 0.3s ease;
position: relative;
&:hover {
background: $theme-color;
}
&:not(:last-child) {
border-bottom: 1px solid $border-color;
transform: translateY(-0.08rem);
filter: drop-shadow(0px 4px 12px rgba(0, 0, 0, 0.06));
}
}
&__td {
padding: 0.16rem 0.2rem;
font-size: 0.13rem;
color: $text-dark;
vertical-align: middle;
&--action {
position: relative;
text-align: center;
opacity: 0;
transition: opacity 0.15s;
}
&--ellipsis {
max-width: 3.6rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
&__row:hover &__td--action {
// hover嵌套选择器
&__card:hover &__card-more-btn {
opacity: 1;
}
// 简历名称单元格
&__name-cell {
display: flex;
align-items: center;
gap: 0.06rem;
&__card:hover &__card-edit-time {
opacity: 1;
height: 0.16rem;
margin-top: 0.04rem;
}
// hover时info向上扩展absolute定位卡片总高度不变
&__card:hover &__card-info {
height: 0.86rem;
}
&__avatar {
width: 0.3rem;
height: 0.3rem;
// 卡片预览图区域
&__card-preview {
width: 100%;
height: 100%;
border-radius: 0.12rem;
background: $bg-white;
overflow: hidden;
position: relative;
img {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 0.12rem;
}
}
// 默认标记左上角
&__card-default-tag {
position: absolute;
top: 0;
left: 0;
background: $accent;
color: #fff;
font-size: 0.12rem;
font-weight: 520;
padding: 0.02rem 0.08rem;
border-radius: 0.12rem 0 0.12rem 0;
z-index: 2;
line-height: 0.16rem;
}
// 更多按钮右上角圆形
&__card-more-btn {
position: absolute;
top: 0.12rem;
right: 0.16rem;
width: 0.32rem;
height: 0.32rem;
background: $bg-white;
border-radius: 50%;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
color: $bg-white;
font-size: 0.13rem;
font-weight: 600;
flex-shrink: 0;
cursor: pointer;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 3;
border: none;
&:hover {
background: #f5f5f5;
}
svg {
width: 0.18rem;
height: 0.18rem;
}
}
&__name {
font-weight: 500;
max-width: 3.6rem;
// 更多菜单弹出层
&__card-menu {
position: absolute;
top: 0.44rem;
right: 0.08rem;
width: 1.04rem;
background: $bg-white;
box-shadow: 0px 0px 12px rgba(0, 0, 0, 0.1);
border-radius: 0.08rem;
padding: 0.06rem 0;
z-index: 10;
}
&__card-menu-item {
display: flex;
align-items: center;
padding: 0.06rem 0.08rem;
gap: 0.04rem;
cursor: pointer;
font-size: 0.14rem;
font-weight: 380;
color: $text-middle;
transition: background 0.15s;
white-space: nowrap;
&:hover {
background: #f9f9f9;
}
// 设为默认特殊样式
&--default {
background: #EDF9FA;
border-radius: 0.04rem;
margin: 0 0.06rem;
padding: 0.06rem 0.08rem;
color: $accent;
&:hover {
background: #ddf3f5;
}
}
// 删除按钮特殊样式
&--delete {
svg {
color: $text-middle;
}
}
svg {
width: 0.16rem;
height: 0.16rem;
flex-shrink: 0;
}
}
// 卡片底部信息区域absolute定位在卡片底部hover时向上扩展卡片总高度不变
&__card-info {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background: $bg-white;
box-shadow: 0px -2px 12px rgba(0, 0, 0, 0.1);
border-radius: 0 0 0.12rem 0.12rem;
padding: 0.12rem;
height: 0.66rem;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: center;
transition: height 0.3s ease;
z-index: 2;
}
// 卡片信息头部名称 + 编辑按钮
&__card-info-header {
display: flex;
align-items: center;
justify-content: space-between;
}
// 简历名称
&__card-name {
font-size: 0.14rem;
font-weight: 450;
color: #000;
max-width: 1.50rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
line-height: 0.19rem;
}
// 编辑图标按钮
&__card-edit-btn {
width: 0.20rem;
height: 0.20rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
background: none;
padding: 0;
&:hover {
opacity: 0.7;
}
svg {
width: 0.14rem;
height: 0.14rem;
}
}
// 目标岗位
&__card-target {
font-size: 0.14rem;
font-weight: 330;
color: $text-middle;
line-height: 0.19rem;
margin-top: 0.04rem;
max-width: 1.80rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__default-tag {
font-size: 0.09rem;
color: $accent;
background: #EDF9FA;
border-radius: 0.04rem;
padding: 0.014rem 0.08rem;
padding-bottom: 0.02rem;
white-space: nowrap;
}
// 编辑名称岗位图标按钮
&__edit-btn {
background: none;
border: none;
color: $text-dark;
cursor: pointer;
padding: 0.02rem;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.2s;
&:hover {
opacity: 0.6;
}
}
&__edit-icon {
font-size: 0.13rem;
}
// 操作按钮容器
&__actions {
display: flex;
align-items: center;
gap: 0.16rem;
justify-content: flex-end;
flex-wrap: wrap;
}
// 行内操作按钮
&__action-btn {
background: none;
border: none;
// 最后编辑时间hover 时显示
&__card-edit-time {
font-size: 0.12rem;
color: $text-middle;
cursor: pointer;
padding: 0.04rem 0.08rem;
border-radius: 0.04rem;
white-space: nowrap;
transition: all 0.15s;
display: flex;
align-items: center;
&:hover {
color: $text-dark;
}
// 删除按钮红色
&--delete {
color: #FD6464;
&:hover {
color: #e04545;
}
}
font-weight: 330;
color: $text-light;
line-height: 0.16rem;
opacity: 0;
height: 0;
margin-top: 0;
overflow: hidden;
transition: opacity 0.3s ease, height 0.3s ease, margin-top 0.3s ease;
}
// 更多按钮已移除保留兼容
&__more-btn {
background: none;
border: none;
color: $text-light;
cursor: pointer;
padding: 0.04rem;
border-radius: 0.04rem;
// ==================== 新建卡片 ====================
&__card-new {
width: 2.10rem;
height: 2.97rem;
border-radius: 0.12rem;
background: $bg-white;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
cursor: pointer;
transition: transform 0.3s ease, box-shadow 0.3s ease;
gap: 0.08rem;
&:hover {
color: $text-dark;
background: $bg-main;
transform: translateY(-0.08rem);
box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.06);
}
}
&__more-svg {
width: 0.16rem;
height: 0.16rem;
svg {
width: 0.32rem;
height: 0.32rem;
}
span {
font-size: 0.18rem;
font-weight: 520;
color: $accent;
line-height: 0.24rem;
}
}
// 空状态提示
@@ -278,6 +311,18 @@
color: $text-light;
font-size: 0.14rem;
}
// 加载中
&__loading-box {
width: 100%;
padding: 0.2rem 0;
display: flex;
align-items: center;
justify-content: center;
gap: 0.08rem;
color: $text-light;
font-size: 0.12rem;
}
}
// ==================== 上传简历加载提示 ====================
+250
View File
@@ -0,0 +1,250 @@
<template>
<!-- 关于我们Tab内容 -->
<div class="home-about">
<!-- Hero 区域 -->
<section class="home-about__hero">
<div class="home-about__hero-inner">
<p class="home-about__hero-kicker">投简历神器 · 一键自动简申 · 求职助力提升</p>
<h1 class="home-about__hero-title">让每一次求职选择<br/>都更有方向</h1>
<p class="home-about__hero-subtitle">以AI提效效率以专业经验支持判断帮助年轻人看见机会理解选择更有准备地走向下一步</p>
<button class="home-about__hero-btn" @click="router.push('/jobs')">了解Offer派</button>
</div>
<!-- 底部居中渐变色块 -->
<div class="home-about__hero-orb"></div>
</section>
<!-- 产品介绍区域 -->
<section class="home-about__intro">
<div class="home-about__intro-inner">
<!-- 左侧产品图片占位 -->
<div class="home-about__intro-img">
<div class="home-about__intro-img-placeholder">
<img src="@/assets/images/home/home-about-01.png" alt="产品介绍" style="width: 100%; height: 100%; object-fit: cover; border-radius: 0.22rem;" />
</div>
</div>
<!-- 右侧文字说明 -->
<div class="home-about__intro-text">
<span class="home-about__kicker">产品介绍</span>
<h2 class="home-about__section-title">用AI让求职更清晰更高效</h2>
<p class="home-about__intro-desc">
求职的问题远远不只是不知道去哪里看招聘信息该投简历方向模糊岗位要求说不清用什么以及充斥着大量无用信息
</p>
<p class="home-about__intro-desc">
Offer派Offerpai是志立要辅导下面向大学生的一站式就业辅导平台帮助毕业生搜索与投递简历找到适合自己的工作采用先进AI技术双科汇集海量岗位信息/平台上下配合让大学生成功投递简历基本覆盖日均200+知名企业在线等你的求职好运气
</p>
<p class="home-about__intro-desc">
我们不急着做赚用户的决定也不求速的回到的求职轨道我们更愿意将技术成为少些信息差和搜索理操作让每个人更关注对他们真的重要的事了解岗位认真复盘适合自己以及加强真正重复投递对接
</p>
</div>
</div>
</section>
<!-- 产品亮点区域 -->
<section class="home-about__features">
<div class="home-about__features-header">
<span class="home-about__kicker">产品亮点</span>
<h2 class="home-about__section-title">让求职的每一步都更简单一点</h2>
<p class="home-about__section-subtitle">找岗位搜索自动简申导师经验和AI理想放在一起让求职不再碎片化</p>
</div>
<!-- 5个功能卡片 -->
<div class="home-about__features-grid">
<div class="home-about__feature-card" v-for="(feat, idx) in featureCards" :key="idx">
<div class="home-about__feature-icon">
<component :is="feat.icon" />
</div>
<h3 class="home-about__feature-title">{{ feat.title }}</h3>
<p class="home-about__feature-desc">{{ feat.desc }}</p>
</div>
</div>
</section>
<!-- 为什么创立区域 -->
<section class="home-about__why">
<div class="home-about__why-header">
<span class="home-about__kicker">我们的初心</span>
<h2 class="home-about__section-title">为什么创立Offer派</h2>
<p class="home-about__section-subtitle">我们希望让专业的求职服务支持离年轻人更近一点</p>
</div>
<!-- 两个内容块左文右图 / 左图右文 -->
<div class="home-about__why-content">
<div class="home-about__why-block">
<div class="home-about__why-text">
<h3>我们希望让好的求职服务离年轻人更近一点</h3>
<p>
团队最初在2016年成立以来一直在帮大学生但提供全方位服务的社会人的接入的门槛太高了我选择什么方向哪家特的合适推荐给我为了轻而且自己应付更简练的记录按录入升级面配不同人开不清情形有那么多的新形式把大家搜索找一种你负面之间也能得到了就入到城打发到实际去请投资让到了意思每一份好资源
</p>
</div>
<div class="home-about__why-img">
<img src="@/assets/images/home/home-about-02.png" alt="为什么选择Offer派" style="width: 100%; height: 100%; object-fit: cover; border-radius: 0.16rem;" />
</div>
</div>
<div class="home-about__why-block home-about__why-block--reverse">
<div class="home-about__why-img">
<img src="@/assets/images/home/home-about-03.png" alt="为什么选择Offer派" style="width: 100%; height: 100%; object-fit: cover; border-radius: 0.16rem;" />
</div>
<div class="home-about__why-text">
<h3>AI 的出现让这件事情变得真正可行</h3>
<p>
当应对AI的求职者让AI变元素更多利把关求职先忙完处然后大部分的力于是做好们开了Offer派我们让数据帮你搜匹配信息便签目的引用布策重要做一些好交流但是做到面确做了另外所有内外对到不人我们不想当自不是经验的出错的"密集完成"而是一个有着力有意思的管用同步去看有未来方向对结
</p>
<p class="home-about__why-sign">Offer派团队</p>
</div>
</div>
</div>
</section>
<!-- 万千毕业生的信赖之选 -->
<section class="home-about__trust">
<div class="home-about__trust-header">
<h2 class="home-about__section-title">源于见识星球也走向更多年轻人</h2>
<p class="home-about__section-subtitle">真实求职反馈见证每一次从迷茫到积上岸</p>
</div>
<!-- 深色卡片 -->
<div class="home-about__trust-dark-card">
<div class="home-about__trust-dark-text">
<p>Offer派是见识星球旗下的AI求职产品</p>
<p>
见识星球创始团队专注于大学生年轻人校招生涯的多年来求职辅导名企求职与职业成长通过自身招聘和辅助加盟者为用户提供更独到的服务专业的求职就业结合到每次经验与能力帮企业用适配员工人选适配让很多在先进面上入大时代初步分析前期做到点求职规时请改变为真正知道来化更好我们是职在服务不做到打的到角色
</p>
<p>
之后我们渐渐不间断继续是指据角色
</p>
</div>
<div class="home-about__trust-dark-logo"></div>
</div>
<!-- 两个白色卡片 -->
<div class="home-about__trust-cards">
<div class="home-about__trust-card">
<h4>Offer </h4>
<p>兼有制度云层级后一键自动简申在立即用持行项服的技术打下一样汇聚汇集到全面就业合力社作育的也指进一步到更精准到更新更定收到先更指连选到关Offer</p>
</div>
<div class="home-about__trust-card">
<h4>见识星球</h4>
<p>专注于大学生专业实力指每的一个校园的到导知网的新打5000+名企公司到得到北大学学专的将步的改之后全入到体入到未来打的到期配到面</p>
</div>
</div>
<!-- 底部总结 -->
<p class="home-about__trust-summary">AI提升效率真人提供判断我们共同陪伴年轻人从发现机会走到理解机会准备机会和抓住机会</p>
</section>
<!-- 价值观区域 -->
<section class="home-about__values">
<div class="home-about__values-header">
<h2 class="home-about__section-title">每个年轻人的选择都值得被认真对待</h2>
<p class="home-about__section-subtitle">我们用技术减少信息差距也用真实经验达客观心界和长期视角帮助年轻人做出更适合自己的决定</p>
</div>
<!-- 4个价值观卡片 -->
<div class="home-about__values-grid">
<div class="home-about__value-card" v-for="(val, idx) in valueCards" :key="idx">
<div class="home-about__value-icon">
<component :is="val.icon" />
</div>
<h3 class="home-about__value-title">{{ val.title }}</h3>
<p class="home-about__value-desc">{{ val.desc }}</p>
</div>
</div>
</section>
<!-- 联系我们区域 -->
<section class="home-about__contact">
<div class="home-about__contact-header">
<span class="home-about__kicker">期待与你一起创建更多可能</span>
<h2 class="home-about__section-title">联系我们</h2>
<p class="home-about__section-subtitle">无论是产品建议岗位信息纠错还是商务合作与导师入驻欢迎与我们联系</p>
</div>
<!-- 3个联系方式卡片 -->
<div class="home-about__contact-grid">
<div class="home-about__contact-card" v-for="(c, idx) in contactCards" :key="idx">
<div class="home-about__contact-icon">
<component :is="c.icon" />
</div>
<div class="home-about__contact-info">
<h4>{{ c.title }}</h4>
<p>{{ c.desc }}</p>
<a class="home-about__contact-link" :href="c.link" target="_blank">{{ c.linkText }} </a>
</div>
</div>
</div>
</section>
<!-- CTA区域 -->
<section class="home-cta">
<div class="home-cta__inner">
<h2>让Offer派带你找到<br/>人生第一份好工作</h2>
<button class="home-cta__btn" @click="router.push('/jobs')">免费体验</button>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { h } from 'vue'
import { useRouter } from 'vue-router'
const router = useRouter()
// ==================== ====================
/** 火箭图标 */
const IconRocket = { render: () => h('svg', { width: '32', height: '32', viewBox: '0 0 32 32', fill: 'none' }, [h('path', { d: 'M8 24C8 24 9.5 22 12 22C14.5 22 16 24 16 24', stroke: '#52CAD1', 'stroke-width': '2', 'stroke-linecap': 'round' }), h('path', { d: 'M16 4C16 4 26 8 26 18C26 24 22 28 16 28C10 28 6 24 6 18C6 8 16 4 16 4Z', stroke: '#52CAD1', 'stroke-width': '2', fill: 'none' })]) }
/** 手指点击图标 */
const IconClick = { render: () => h('svg', { width: '32', height: '32', viewBox: '0 0 32 32', fill: 'none' }, [h('path', { d: 'M16 6V18M16 18L20 22M16 18L12 22M10 12C7 12 6 14 6 16C6 22 10 26 16 26C22 26 26 22 26 16C26 14 25 12 22 12', stroke: '#52CAD1', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' })]) }
/** 黑板教师图标 */
const IconTeacher = { render: () => h('svg', { width: '32', height: '32', viewBox: '0 0 32 32', fill: 'none' }, [h('rect', { x: '4', y: '6', width: '24', height: '16', rx: '2', stroke: '#52CAD1', 'stroke-width': '2', fill: 'none' }), h('path', { d: 'M12 26H20M16 22V26', stroke: '#52CAD1', 'stroke-width': '2', 'stroke-linecap': 'round' })]) }
/** 纸飞机图标 */
const IconSend = { render: () => h('svg', { width: '32', height: '32', viewBox: '0 0 32 32', fill: 'none' }, [h('path', { d: 'M6 16L26 6L20 26L16 18L6 16Z', stroke: '#52CAD1', 'stroke-width': '2', 'stroke-linejoin': 'round', fill: 'none' }), h('path', { d: 'M16 18L26 6', stroke: '#52CAD1', 'stroke-width': '2' })]) }
/** AI图标 */
const IconAI = { render: () => h('svg', { width: '32', height: '32', viewBox: '0 0 32 32', fill: 'none' }, [h('rect', { x: '6', y: '8', width: '20', height: '16', rx: '4', stroke: '#52CAD1', 'stroke-width': '2', fill: 'none' }), h('text', { x: '16', y: '20', 'text-anchor': 'middle', fill: '#52CAD1', 'font-size': '10', 'font-weight': '600' }, 'AI')]) }
/** 功能卡片列表 */
const featureCards = [
{ icon: IconRocket, title: '投简历神器', desc: '聚合数十个主流招聘网站的大学生校招信息,包含岗位分析、行业解读、精准投递帮助推荐求职。一键批量投递配合提高效率。' },
{ icon: IconClick, title: '一键自动简申', desc: '基本以自动化来填简历投递,自动获取表单,跟你配合简申计划进行1-2步。' },
{ icon: IconTeacher, title: '名企老师VI辅导', desc: 'Offer派定期邀请企业在职的一线人员,从他们自身去分享。业内包括做,关注搜索、指的的到实际Offer!' },
{ icon: IconSend, title: '内推发抖无限资', desc: '结合真实的导师在线直聊、资料指助获的从专业知识把关。' },
{ icon: IconAI, title: '全方面AI求职助手', desc: '接入当前最强AI大模型GPT-4o,智能自动对话的全方位匹配的面完整的更的线的全面的NOVA在最有最终的但能这的内的的。' },
]
// ==================== ====================
/** 雷达图标 */
const IconRadar = { render: () => h('svg', { width: '48', height: '48', viewBox: '0 0 48 48', fill: 'none' }, [h('circle', { cx: '24', cy: '24', r: '18', stroke: '#111', 'stroke-width': '3', fill: 'none' }), h('circle', { cx: '24', cy: '24', r: '10', stroke: '#111', 'stroke-width': '3', fill: 'none' }), h('circle', { cx: '24', cy: '24', r: '3', fill: '#111' }), h('path', { d: 'M24 6V24L36 12', stroke: '#111', 'stroke-width': '3', 'stroke-linecap': 'round' })]) }
/** 扳手图标 */
const IconWrench = { render: () => h('svg', { width: '48', height: '48', viewBox: '0 0 48 48', fill: 'none' }, [h('path', { d: 'M12 36L30 18M30 18C28 14 30 10 34 8C34 8 36 12 40 12C40 16 36 18 34 18L30 18Z', stroke: '#111', 'stroke-width': '3', 'stroke-linecap': 'round', 'stroke-linejoin': 'round', fill: 'none' }), h('path', { d: 'M12 36C10 38 10 40 12 42C14 44 16 44 18 42L12 36Z', stroke: '#111', 'stroke-width': '3', fill: 'none' })]) }
/** 路标图标 */
const IconSignpost = { render: () => h('svg', { width: '48', height: '48', viewBox: '0 0 48 48', fill: 'none' }, [h('path', { d: 'M24 8V42', stroke: '#111', 'stroke-width': '3', 'stroke-linecap': 'round' }), h('path', { d: 'M12 14H32L36 18L32 22H12V14Z', stroke: '#111', 'stroke-width': '3', fill: 'none' }), h('path', { d: 'M36 28H16L12 32L16 36H36V28Z', stroke: '#111', 'stroke-width': '3', fill: 'none' })]) }
/** 星球图标 */
const IconPlanet = { render: () => h('svg', { width: '48', height: '48', viewBox: '0 0 48 48', fill: 'none' }, [h('circle', { cx: '24', cy: '24', r: '12', stroke: '#111', 'stroke-width': '3', fill: 'none' }), h('ellipse', { cx: '24', cy: '24', rx: '20', ry: '8', stroke: '#111', 'stroke-width': '3', fill: 'none', transform: 'rotate(-30 24 24)' })]) }
const valueCards = [
{ icon: IconRadar, title: '机会不应该被信息差阻隔', desc: '我们努力让更多元宝、优质的招聘机会被年轻人及时看见。' },
{ icon: IconWrench, title: '技术应该服务真实需求', desc: '我们不会为了使用AI而使用AI,每一项功能都应该直接使切,提高用户本体验和知和用户效率互打的方面' },
{ icon: IconPlanet, title: '求职不只是获得一份工作', desc: '一份工作选着一个人未来几年的生活。我们决定后的Offer,也作进每个人长期的发展方向。' },
{ icon: IconSignpost, title: '专业建议也要尊重个人选择', desc: '我们提供信息、工具与经验,但不替用户做决定。适合自己的选择,比统一的标准效果更重要' },
]
// ==================== ====================
/** 产品反馈图标 */
const IconFeedback = { render: () => h('svg', { width: '32', height: '32', viewBox: '0 0 32 32', fill: 'none' }, [h('path', { d: 'M6 8H26V22H18L12 26V22H6V8Z', stroke: '#52CAD1', 'stroke-width': '2', 'stroke-linejoin': 'round', fill: 'none' }), h('circle', { cx: '12', cy: '15', r: '1.5', fill: '#52CAD1' }), h('circle', { cx: '16', cy: '15', r: '1.5', fill: '#52CAD1' }), h('circle', { cx: '20', cy: '15', r: '1.5', fill: '#52CAD1' })]) }
/** 握手图标 */
const IconHandshake = { render: () => h('svg', { width: '32', height: '32', viewBox: '0 0 32 32', fill: 'none' }, [h('path', { d: 'M4 14L10 10L16 14L22 10L28 14', stroke: '#52CAD1', 'stroke-width': '2', 'stroke-linecap': 'round', 'stroke-linejoin': 'round' }), h('path', { d: 'M10 10V22M22 10V22', stroke: '#52CAD1', 'stroke-width': '2', 'stroke-linecap': 'round' })]) }
/** 导师入驻图标 */
const IconMentor = { render: () => h('svg', { width: '32', height: '32', viewBox: '0 0 32 32', fill: 'none' }, [h('rect', { x: '4', y: '6', width: '24', height: '16', rx: '2', stroke: '#52CAD1', 'stroke-width': '2', fill: 'none' }), h('circle', { cx: '16', cy: '13', r: '3', stroke: '#52CAD1', 'stroke-width': '2', fill: 'none' }), h('path', { d: 'M10 20C10 17 13 16 16 16C19 16 22 17 22 20', stroke: '#52CAD1', 'stroke-width': '2', 'stroke-linecap': 'round' }), h('path', { d: 'M12 26H20', stroke: '#52CAD1', 'stroke-width': '2', 'stroke-linecap': 'round' })]) }
const contactCards = [
{ icon: IconFeedback, title: '产品反馈', desc: '遇到任何问题,我们会全力解决和修正。留言反馈,欢迎告诉我们!', link: 'javascript:void(0)', linkText: '提交反馈' },
{ icon: IconHandshake, title: '商务合作', desc: '企业招聘、共赢合作、技术开发等商业合作。更进一步了解我们的服务', link: 'javascript:void(0)', linkText: '查看简历' },
{ icon: IconMentor, title: '导师入驻', desc: '如果你从来资深的行业专业导师,你可以求职领域服务更多年人。欢迎联入导师申请联系。', link: 'javascript:void(0)', linkText: '申请入驻' },
]
</script>
+367
View File
@@ -0,0 +1,367 @@
<template>
<!-- 校招公告Tab内容 -->
<div class="home-announcement">
<!-- 第一部分校招公告一站查全 -->
<section class="home-announcement__section">
<div class="home-announcement__header">
<div class="">
<h2 class="home-announcement__title">校招公告一站查全</h2>
<p class="home-announcement__subtitle">汇集企业官网公众号和招聘专题页优先看今天新动态</p>
</div>
<!-- 首页版校招动态统计卡片 -->
<div class="home-announcement__stats-card">
<!-- 标题区域 -->
<div class="home-announcement__stats-title-wrap">
<span class="home-announcement__stats-title">岗位概览</span>
<span class="home-announcement__stats-date">数据截至 {{ statsCurrentDate }}</span>
</div>
<!-- 数据区域 -->
<div class="home-announcement__stats-data">
<!-- 今日新增 -->
<div class="home-announcement__stats-item">
<span class="home-announcement__stats-num">{{ statsTodayCount }}</span>
<span class="home-announcement__stats-label">今日新增</span>
</div>
<!-- 分割线 -->
<div class="home-announcement__stats-divider"></div>
<!-- 本月新增 -->
<div class="home-announcement__stats-item">
<span class="home-announcement__stats-num">{{ statsMonthCount }}</span>
<span class="home-announcement__stats-label">本月新增</span>
</div>
<!-- 分割线 -->
<div class="home-announcement__stats-divider"></div>
<!-- 累计收录 -->
<div class="home-announcement__stats-item">
<span class="home-announcement__stats-num">{{ statsTotalCount }}</span>
<span class="home-announcement__stats-label">累计收录</span>
</div>
</div>
</div>
</div>
<!-- 筛选面板与岗位雷达一致 -->
<div class="home-announcement__filters">
<!-- 行业筛选行 -->
<div class="home-announcement__filter-row">
<span class="home-announcement__filter-label">行业</span>
<div class="home-announcement__filter-chips">
<span
class="home-announcement__chip"
:class="{ 'home-announcement__chip--active': selectedIndustryName === null }"
@click="selectIndustry(null)"
>全部</span>
<span
v-for="item in hotIndustries"
:key="'ind-' + item.id"
class="home-announcement__chip"
:class="{ 'home-announcement__chip--active': selectedIndustryName === item.name }"
@click="selectIndustry(item.name)"
>{{ item.name }}</span>
</div>
</div>
<!-- 城市筛选行 -->
<div class="home-announcement__filter-row">
<span class="home-announcement__filter-label">城市</span>
<div class="home-announcement__filter-chips">
<span
class="home-announcement__chip"
:class="{ 'home-announcement__chip--active': selectedCityName === null }"
@click="selectCity(null)"
>全部</span>
<span
v-for="item in hotCities"
:key="'city-' + item.code"
class="home-announcement__chip"
:class="{ 'home-announcement__chip--active': selectedCityName === item.name }"
@click="selectCity(item.name)"
>{{ item.name }}</span>
</div>
</div>
<!-- 岗位筛选行 -->
<div class="home-announcement__filter-row">
<span class="home-announcement__filter-label">岗位</span>
<div class="home-announcement__filter-chips">
<span
class="home-announcement__chip"
:class="{ 'home-announcement__chip--active': selectedCategoryName === null }"
@click="selectCategory(null)"
>全部</span>
<span
v-for="item in hotCategories"
:key="'cat-' + item.id"
class="home-announcement__chip"
:class="{ 'home-announcement__chip--active': selectedCategoryName === item.name }"
@click="selectCategory(item.name)"
>{{ item.name }}</span>
</div>
</div>
</div>
<!-- 公告列表网格 -->
<div class="home-announcement__grid">
<div
v-for="(item, index) in announcementList"
:key="index"
class="home-announcement__card"
@click="goToDetail(item)"
>
<!-- 卡片主体 -->
<div class="home-announcement__card-body">
<!-- 左侧公司logo -->
<div class="home-announcement__card-logo-wrap">
<img v-if="item.logoUrl" class="home-announcement__card-logo" :src="item.logoUrl" alt="" />
<div v-else class="home-announcement__card-logo home-announcement__card-logo--default">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="4" fill="#EDF9FA"/><g clip-path="url(#clogo3)"><path d="M5.99998 16.6667H6.66665V8.24001C6.66667 8.0301 6.73274 7.82552 6.8555 7.65526C6.97827 7.48499 7.15149 7.35767 7.35065 7.29134L12.684 5.51401C12.8343 5.46395 12.9943 5.45031 13.1509 5.47423C13.3076 5.49815 13.4562 5.55893 13.5848 5.65157C13.7133 5.74421 13.8179 5.86606 13.8901 6.00708C13.9623 6.1481 14 6.30425 14 6.46268V16.6667H14.6666V10.66C14.6667 10.6107 14.6776 10.562 14.6987 10.5175C14.7199 10.4729 14.7506 10.4336 14.7887 10.4024C14.8268 10.3711 14.8714 10.3487 14.9193 10.3368C14.9671 10.3249 15.017 10.3237 15.0653 10.3333L16.5293 10.6267C16.7559 10.672 16.9599 10.7944 17.1064 10.973C17.253 11.1517 17.3332 11.3756 17.3333 11.6067V16.6667H18C18.1768 16.6667 18.3464 16.7369 18.4714 16.8619C18.5964 16.987 18.6666 17.1565 18.6666 17.3333C18.6666 17.5102 18.5964 17.6797 18.4714 17.8047C18.3464 17.9298 18.1768 18 18 18H5.99998C5.82317 18 5.6536 17.9298 5.52858 17.8047C5.40355 17.6797 5.33331 17.5102 5.33331 17.3333C5.33331 17.1565 5.40355 16.987 5.52858 16.8619C5.6536 16.7369 5.82317 16.6667 5.99998 16.6667Z" fill="#52CAD1"/></g><defs><clipPath id="clogo3"><rect width="16" height="16" fill="white" transform="translate(4 4)"/></clipPath></defs></svg>
</div>
</div>
<!-- 右侧内容 -->
<div class="home-announcement__card-content">
<!-- 标题行 + 公司类型标签 -->
<div class="home-announcement__card-title-row">
<span class="home-announcement__card-title">{{ item.title }}</span>
<span v-if="getCompanyType(item)" class="home-announcement__card-type-tag">{{ getCompanyType(item) }}</span>
</div>
<!-- 标签行批次 + 学历 -->
<div class="home-announcement__card-tags">
<span v-if="item.batchNames && item.batchNames.length" class="home-announcement__card-tag home-announcement__card-tag--batch">{{ item.batchNames[0] }}</span>
<span v-if="item.educationNames && item.educationNames.length" class="home-announcement__card-tag home-announcement__card-tag--edu">{{ item.educationNames[0] }}</span>
</div>
<!-- 岗位信息行 -->
<div class="home-announcement__card-info-line">
<svg class="home-announcement__card-icon" viewBox="0 0 16 16" fill="none">
<path d="M2.66669 12H13.3334V5.33333H2.66669V12ZM2.66669 4H13.3334V3.33333H2.66669V4ZM2.66669 12.6667C2.30002 12.6667 1.98602 12.5362 1.72469 12.2753C1.46402 12.0138 1.33335 11.6998 1.33335 11.3333V3.33333C1.33335 2.96667 1.46402 2.65289 1.72469 2.392C1.98602 2.13067 2.30002 2 2.66669 2H13.3334C13.7 2 14.014 2.13067 14.2754 2.392C14.5361 2.65289 14.6667 2.96667 14.6667 3.33333V11.3333C14.6667 11.6998 14.5361 12.0138 14.2754 12.2753C14.014 12.5362 13.7 12.6667 13.3334 12.6667H2.66669ZM4.66669 14H11.3334V13.3333H4.66669V14Z" fill="#64748B"/>
</svg>
<span>{{ formatCategories(item) }}</span>
</div>
<!-- 城市信息行 -->
<div class="home-announcement__card-info-line">
<svg class="home-announcement__card-icon" viewBox="0 0 16 16" fill="none">
<path d="M8 8.66667C8.36667 8.66667 8.68067 8.53622 8.942 8.27533C9.20289 8.01400 9.33333 7.7 9.33333 7.33333C9.33333 6.96667 9.20289 6.65289 8.942 6.392C8.68067 6.13067 8.36667 6 8 6C7.63333 6 7.31956 6.13067 7.05867 6.392C6.79733 6.65289 6.66667 6.96667 6.66667 7.33333C6.66667 7.7 6.79733 8.01400 7.05867 8.27533C7.31956 8.53622 7.63333 8.66667 8 8.66667ZM8 13.3C9.35556 12.0667 10.3751 10.9527 11.0587 9.958C11.7418 8.96378 12.0833 8.08889 12.0833 7.33333C12.0833 6.17778 11.7084 5.22222 10.9587 4.46667C10.2084 3.71111 9.2 3.33333 8 3.33333C6.8 3.33333 5.79156 3.71111 4.94133 4.46667C4.09156 5.22222 3.91667 6.17778 3.91667 7.33333C3.91667 8.08889 4.25844 8.96378 4.942 9.958C5.62511 10.9527 6.64444 12.0667 8 13.3Z" fill="#64748B"/>
</svg>
<span>{{ formatCities(item) }}</span>
</div>
</div>
</div>
<!-- 卡片底部渐变条 -->
<div class="home-announcement__card-footer">
<span class="home-announcement__card-update">{{ formatUpdateStatus(item) }}</span>
<span class="home-announcement__card-deadline">{{ formatDeadline(item) }}</span>
</div>
</div>
</div>
<!-- 加载中 -->
<div v-if="loading" class="home-announcement__loading">
<div class="home-announcement__spinner"></div>
<span>加载中...</span>
</div>
<!-- 查看更多按钮 -->
<div v-if="!loading && announcementList.length > 0" class="home-announcement__more">
<button class="home-announcement__more-btn" @click="router.push('/announcement')">查看更多</button>
</div>
</section>
<!-- 第二部分CTA区域 -->
<section class="home-cta">
<div class="home-cta__inner">
<h2>让Offer派带你找到<br/>人生第一份好工作</h2>
<button class="home-cta__btn" @click="router.push('/announcement')">免费体验</button>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import { fetchAnnouncementList, fetchAnnouncementLastUpdateTime, fetchAnnouncementStatistics } from '@/api/announcement'
import type { AnnouncementItem } from '@/api/announcement'
import { fetchHotIndustries, fetchHotCategories, fetchHotCities } from '@/api/jobs'
import type { HotIndustryItem, HotCategoryItem, HotCityItem } from '@/api/jobs'
/** 路由实例 */
const router = useRouter()
// ==================== ====================
/** 最新更新时间(MM.DD 格式) */
const statsCurrentDate = ref('--')
/** 今日新增 */
const statsTodayCount = ref<string | number>('--')
/** 本月新增 */
const statsMonthCount = ref<string | number>('--')
/** 累计收录 */
const statsTotalCount = ref<string | number>('--')
/** 加载统计卡片数据 */
async function loadStatsData() {
try {
const [timeRes, statsRes] = await Promise.all([
fetchAnnouncementLastUpdateTime(),
fetchAnnouncementStatistics(),
])
if (timeRes.code === '0' && timeRes.data) {
const date = new Date(Number(timeRes.data))
statsCurrentDate.value = `${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`
}
if (statsRes.code === '0' && statsRes.data) {
statsTodayCount.value = statsRes.data.dayCount ?? 0
statsMonthCount.value = statsRes.data.monthCount ?? 0
statsTotalCount.value = statsRes.data.totalCount ?? 0
}
} catch (e) {
console.error('加载统计数据失败', e)
}
}
// ==================== ====================
/** 热门行业列表 */
const hotIndustries = ref<HotIndustryItem[]>([])
/** 热门城市列表 */
const hotCities = ref<HotCityItem[]>([])
/** 热门岗位列表 */
const hotCategories = ref<HotCategoryItem[]>([])
/** 当前选中的行业名称(null=全部) */
const selectedIndustryName = ref<string | null>(null)
/** 当前选中的城市名称(null=全部) */
const selectedCityName = ref<string | null>(null)
/** 当前选中的岗位名称(null=全部) */
const selectedCategoryName = ref<string | null>(null)
// ==================== ====================
/** 公告列表数据 */
const announcementList = ref<AnnouncementItem[]>([])
/** 加载状态 */
const loading = ref(false)
/** 选择行业 */
function selectIndustry(name: string | null) {
selectedIndustryName.value = name
}
/** 选择城市 */
function selectCity(name: string | null) {
selectedCityName.value = name
}
/** 选择岗位分类 */
function selectCategory(name: string | null) {
selectedCategoryName.value = name
}
/** 加载公告列表(公告接口传名称) */
async function loadAnnouncementList() {
loading.value = true
try {
const params: any = {
pageNum: 1,
pageSize: 30,
}
if (selectedIndustryName.value !== null) {
params.industryNames = [selectedIndustryName.value]
}
if (selectedCityName.value !== null) {
params.cityNames = [selectedCityName.value]
}
if (selectedCategoryName.value !== null) {
params.categoryNames = [selectedCategoryName.value]
}
const res = await fetchAnnouncementList(params)
if (res.code === '0' && res.data) {
announcementList.value = res.data.list || []
}
} catch (e) {
console.error('加载校招公告列表失败', e)
} finally {
loading.value = false
}
}
/** 格式化岗位分类显示 */
function formatCategories(item: AnnouncementItem): string {
if (!item.categoryNames || item.categoryNames.length === 0) return '暂无岗位信息'
const maxShow = 3
const shown = item.categoryNames.slice(0, maxShow).join(' / ')
const totalCount = item.categoryNames.length
if (totalCount <= maxShow) return shown
return `${shown}${totalCount}`
}
/** 格式化城市显示 */
function formatCities(item: AnnouncementItem): string {
if (!item.cityNames || item.cityNames.length === 0) return '全国'
const maxShow = 3
const shown = item.cityNames.slice(0, maxShow).join(' / ')
const totalCount = item.cityNames.length
if (totalCount <= maxShow) return shown
return `${shown}${totalCount}`
}
/** 获取公司类型(从批次名中推断,暂用批次标签展示) */
function getCompanyType(_item: AnnouncementItem): string {
//
return ''
}
/** 格式化更新状态 */
function formatUpdateStatus(item: AnnouncementItem): string {
if (!item.publishTime) return ''
const now = new Date()
const pub = new Date(item.publishTime)
const diffDays = Math.floor((now.getTime() - pub.getTime()) / (1000 * 60 * 60 * 24))
if (diffDays === 0) return '今日更新'
if (diffDays === 1) return '昨日更新'
if (diffDays <= 7) return `${diffDays}天前更新`
const month = String(pub.getMonth() + 1).padStart(2, '0')
const day = String(pub.getDate()).padStart(2, '0')
return `${month}-${day}更新`
}
/** 格式化截止时间 */
function formatDeadline(item: AnnouncementItem): string {
if (item.applyEndDesc) return item.applyEndDesc
if (!item.applyEndTime || !item.applyEndTime.seconds) return '招满即止'
const date = new Date(item.applyEndTime.seconds * 1000)
if (isNaN(date.getTime())) return '招满即止'
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${month}.${day}截止`
}
/** 跳转公告详情 */
function goToDetail(item: AnnouncementItem) {
router.push({ path: `/announcement/${item.id}`, query: { from: 'home' } })
}
/** 加载热门筛选数据 */
async function loadHotFilters() {
try {
const [indRes, cityRes, catRes] = await Promise.all([
fetchHotIndustries(),
fetchHotCities(),
fetchHotCategories(),
])
if (indRes.code === '0' && indRes.data) hotIndustries.value = indRes.data
if (cityRes.code === '0' && cityRes.data) hotCities.value = cityRes.data
if (catRes.code === '0' && catRes.data) hotCategories.value = catRes.data
} catch (e) {
console.error('加载热门筛选数据失败', e)
}
}
//
watch([selectedIndustryName, selectedCityName, selectedCategoryName], () => {
loadAnnouncementList()
})
onMounted(() => {
loadHotFilters()
loadAnnouncementList()
loadStatsData()
})
</script>
+503
View File
@@ -0,0 +1,503 @@
<template>
<!-- 首页Tab内容 - 从Home.vue抽取的主体内容 -->
<div class="home-content">
<!-- Hero 主视觉区 -->
<section class="home-hero">
<!-- 底层背景色块 -->
<div class="home-hero__orb home-hero__orb--top"></div>
<div class="home-hero__orb home-hero__orb--bottom"></div>
<div class="home-hero__inner">
<div class="home-hero__left">
<h1 class="home-hero__title">Offer派<br/><span class="dib"></span>收offer就是快</h1>
<p class="home-hero__desc">智能匹配职位自动填写申请量身定制简历推荐内部人脉<br/>不到1分钟统统搞定</p>
<button class="home-hero__cta" @click="router.push('/jobs')">免费体验</button>
</div>
<!-- 右侧视频展示区 -->
<div class="home-hero__right">
<div>
<img class="w706 h452" src="@/assets/images/home/home-top.gif" alt="">
</div>
</div>
</div>
</section>
<!-- Stats Section -->
<section class="home-stats">
<div class="home-stats__inner">
<div class="home-stats__header">
<h2 class="fs38">让每一次求职努力<br/>都成为看得见的成果</h2>
<div class="home-stats__divider"></div>
</div>
<div class="home-stats__cards">
<article class="stat-card">
<div class="stat-card__num">第一</div>
<p class="stat-card__label">84.5%大学生求职首选</p>
</article>
<article class="stat-card">
<div class="stat-card__num">10</div>
<p class="stat-card__label">面试邀约率提升</p>
</article>
<article class="stat-card">
<div class="stat-card__num">82%</div>
<p class="stat-card__label">节约82%校招求职时间</p>
</article>
</div>
</div>
</section>
<!-- Jobs Showcase Section -->
<section class="home-jobs-showcase">
<div class="home-jobs-showcase__orb home-jobs-showcase__orb--top"></div>
<div class="home-jobs-showcase__orb home-jobs-showcase__orb--bottom"></div>
<div class="home-jobs-showcase__inner">
<h2 class="fs38">海量优质校招岗位尽在Offer派</h2>
<p class="home-jobs-showcase__sub">实时汇集海量超10000+名校校招职位</p>
<div class="home-jobs-showcase__box prl25">
<div class="home-jobs-showcase__stats">
<div class="showcase-stat">
<div class="showcase-stat__num"><span><span class="accent">40</span><span class="accent"></span></span><span class="fs32">+</span></div>
<div class="showcase-stat__label">岗位总数</div>
</div>
<div class="showcase-stat">
<div class="showcase-stat__num"><span class="accent">{{ dailyJobCount }}</span><span class=""></span></div>
<div class="showcase-stat__label">今日更新</div>
</div>
</div>
<div class="home-jobs-showcase__scroll">
<div class="ticker-track">
<div class="job-ticker" v-for="(job, i) in tickerJobs" :key="'a' + i">
<span class="job-ticker__company">{{ job.company }}·{{ job.time }}</span>
<span class="job-ticker__title">{{ job.title }}</span>
</div>
<div class="job-ticker" v-for="(job, i) in tickerJobs" :key="'b' + i">
<span class="job-ticker__company">{{ job.company }}·{{ job.time }}</span>
<span class="job-ticker__title">{{ job.title }}</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Feature 1: 个性化岗位匹配 -->
<section class="home-feature">
<div class="home-feature__inner">
<div class="home-feature__text">
<h2>个性化智能<br/>岗位匹配</h2>
<p>第一时间发现真正适合你的岗位精准匹配你的真<br/>实技能杜绝虚假信息</p>
<button class="home-feature__btn" @click="router.push('/jobs')">
<svg width="12" height="12" viewBox="0 0 14 23" fill="none"><circle cx="10" cy="10" r="8" stroke="currentColor" stroke-width="2"/><circle cx="10" cy="10" r="4" stroke="currentColor" stroke-width="2"/><circle cx="10" cy="10" r="1.5" fill="currentColor"/></svg>
<span>立即匹配</span>
</button>
</div>
<div class="home-feature__visual home-feature__visual--match">
<div class="feature-match-card">
<div class="feature-match-card__header">
<div class="feature-match-card__avatar"><img class="wp100 hp100" src="@/assets/images/home/alex-avatar.png" alt=""></div>
<div class="feature-match-card__info">
<h4>求职者 Alex</h4>
<div class="feature-match-card__tags">
<span>React</span><span>Tailwind</span>
</div>
</div>
<div class="feature-match-card__score">98%</div>
</div>
<div class="feature-match-card__bars">
<div class="bar bar--full"></div>
<div class="bar bar--3q"></div>
</div>
<div class="feature-match-card__result">为您匹配了 5 个高星职位</div>
</div>
</div>
</div>
</section>
<!-- Feature 2: 一键自动网申 -->
<section class="home-feature home-feature--reverse">
<div class="home-feature__inner">
<div class="home-feature__text">
<h2>校招一键<br/>自动网申</h2>
<p>每日向数百岗位一键投递覆盖各大企业网申系<br/>告别重复填写节省 82% 校招求职时间</p>
<button class="home-feature__btn" @click="router.push('/jobs')">
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 20 21" fill="none"><path d="M3.33331 11.6667C3.17561 11.6673 3.021 11.623 2.88743 11.5392C2.75387 11.4554 2.64683 11.3354 2.57876 11.1931C2.51069 11.0509 2.48438 10.8922 2.50289 10.7356C2.52139 10.579 2.58396 10.4309 2.68331 10.3084L10.9333 1.80839C10.9952 1.73696 11.0795 1.68868 11.1725 1.6715C11.2654 1.65431 11.3614 1.66923 11.4447 1.71381C11.5281 1.75838 11.5938 1.82997 11.6311 1.91681C11.6684 2.00365 11.675 2.10059 11.65 2.19172L10.05 7.20839C10.0028 7.33466 9.98695 7.47049 10.0038 7.60423C10.0206 7.73797 10.0697 7.86562 10.1467 7.97624C10.2237 8.08686 10.3265 8.17715 10.446 8.23936C10.5656 8.30156 10.6985 8.33383 10.8333 8.33339L16.6666 8.33339C16.8243 8.33285 16.9789 8.37707 17.1125 8.46091C17.2461 8.54474 17.3531 8.66476 17.4212 8.80701C17.4893 8.94926 17.5156 9.1079 17.4971 9.26451C17.4785 9.42111 17.416 9.56926 17.3166 9.69172L9.06664 18.1917C9.00476 18.2632 8.92042 18.3114 8.82749 18.3286C8.73455 18.3458 8.63854 18.3309 8.5552 18.2863C8.47186 18.2417 8.40615 18.1701 8.36886 18.0833C8.33157 17.9965 8.32491 17.8995 8.34997 17.8L9.94997 12.7917C9.99715 12.6655 10.013 12.5296 9.99615 12.3959C9.9793 12.2621 9.93026 12.1345 9.85323 12.0239C9.77621 11.9132 9.6735 11.823 9.55391 11.7608C9.43433 11.6985 9.30144 11.6663 9.16664 11.6667L3.33331 11.6667Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"></path></svg>
<span>开启自动网申</span>
</button>
</div>
<div class="home-feature__visual home-feature__visual--apply">
<div class="feature-apply-card">
<div class="feature-apply-card__header">
<span class="feature-apply-card__status">正在执行投递程序...</span>
<div class="feature-apply-card__dots">
<span class="dot dot--red"></span>
<span class="dot dot--yellow"></span>
<span class="dot dot--green"></span>
</div>
</div>
<div class="feature-apply-card__list">
<div class="apply-item">
<div class="apply-item__icon"><img class="wp100 hp100" src="@/assets/images/home/right-icon-01.png" alt=""></div>
<div class="apply-item__info">
<p class="apply-item__title">腾讯科技 - 校招投递成功</p>
<p class="apply-item__sub">耗时 0.8s · 自动识别填写完毕</p>
</div>
</div>
<div class="apply-item apply-item--active">
<div class="apply-item__icon apply-item__icon--pulse"><img class="wp100 hp100" src="@/assets/images/home/load-icon-01.png" alt=""></div>
<div class="apply-item__info">
<p class="apply-item__title">京东校招 - 自动填写中...</p>
<p class="apply-item__sub">进度 65%</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Feature 3: 岗位定制简历 -->
<section class="home-feature">
<div class="home-feature__inner">
<div class="home-feature__text">
<h2>岗位定制简历</h2>
<p>10秒内生成针对特定岗位优化的专业简历通过<br/>ATS系统突出你的核心优势</p>
<button class="home-feature__btn" @click="router.push('/jobs')">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 20 22" fill="none"><path stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M10.5492 18.3334L15 18.3334C15.442 18.3334 15.8659 18.1578 16.1785 17.8453C16.4911 17.5327 16.6667 17.1088 16.6667 16.6668L16.6667 6.66675C16.6673 6.40268 16.6157 6.14109 16.5147 5.89708C16.4137 5.65307 16.2654 5.43148 16.0783 5.24509L13.0883 2.25509C12.9019 2.06802 12.6803 1.91972 12.4363 1.81874C12.1923 1.71776 11.9307 1.66611 11.6667 1.66675L4.99999 1.66675C4.55797 1.66675 4.13404 1.84235 3.82148 2.15491C3.50892 2.46747 3.33333 2.89139 3.33333 3.33342L3.33333 11.1168"></path><path stroke="rgba(82, 202, 209, 1)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M11.6667 1.66675L11.6667 5.83341C11.6667 6.05443 11.7545 6.26639 11.9107 6.42267C12.067 6.57895 12.279 6.66675 12.5 6.66675L16.6667 6.66675"></path><path d="M8.64834 10.5184C8.98019 10.1869 9.43015 10.0007 9.89922 10.001C10.3683 10.0012 10.8181 10.1878 11.1496 10.5196C11.4811 10.8515 11.6672 11.3014 11.667 11.7705C11.6668 12.2396 11.4802 12.6894 11.1483 13.0209L6.96667 17.1975C6.76865 17.3957 6.52389 17.5407 6.255 17.6192L3.86584 18.3167C3.79413 18.3376 3.71812 18.3389 3.64576 18.3203C3.5734 18.3018 3.50736 18.2642 3.45454 18.2113C3.40173 18.1585 3.36408 18.0925 3.34554 18.0201C3.327 17.9478 3.32826 17.8718 3.34917 17.8L4.04584 15.4092C4.12448 15.1406 4.2695 14.8962 4.4675 14.6984L8.64834 10.5184Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"></path></svg>
<span>优化我的简历</span>
</button>
</div>
<div class="home-feature__visual home-feature__visual--resume">
<div class="feature-resume-card">
<div class="feature-resume-card__doc--border">
<div class="feature-resume-card__doc">
<div class="doc-header">
<div class="doc-avatar"></div>
<div class="doc-lines">
<div class="doc-line doc-line--half"></div>
<div class="doc-line doc-line--third"></div>
</div>
</div>
<div class="doc-bars">
<div class="doc-bar doc-bar--1"></div>
<div class="doc-bar doc-bar--2"></div>
<div class="doc-bar doc-bar--3"></div>
</div>
</div>
</div>
<div class="feature-resume-card__badge">
<img class="w15 h15" src="@/assets/images/home/blue-shield-icon-01.png" alt="">
<span>ATS OPTIMIZED</span>
</div>
</div>
</div>
</div>
</section>
<!-- Feature 4: 内推人脉 -->
<section class="home-feature home-feature--reverse">
<div class="home-feature__inner">
<div class="home-feature__text">
<h2>名企内推<br/>人脉直通</h2>
<p>实时获取名企最新内推信息自动填写网申内推<br/>简历更快到达HR</p>
<button class="home-feature__btn" @click="router.push('/jobs')">
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 20 20" fill="none"><path d="M9.16667 5C11.7011 5.06529 14.1783 4.23959 16.1667 2.66667C16.2905 2.57381 16.4377 2.51727 16.5918 2.50337C16.746 2.48947 16.9009 2.51877 17.0393 2.58798C17.1778 2.65719 17.2942 2.76358 17.3755 2.89522C17.4569 3.02687 17.5 3.17857 17.5 3.33333L17.5 13.3333C17.5 13.4881 17.4569 13.6398 17.3755 13.7714C17.2942 13.9031 17.1778 14.0095 17.0393 14.0787C16.9009 14.1479 16.746 14.1772 16.5918 14.1633C16.4377 14.1494 16.2905 14.0929 16.1667 14C14.1783 12.4271 11.7011 11.6014 9.16667 11.6667L4.16667 11.6667C3.72464 11.6667 3.30072 11.4911 2.98816 11.1785C2.67559 10.866 2.5 10.442 2.5 10L2.5 6.66667C2.5 6.22464 2.67559 5.80072 2.98816 5.48816C3.30072 5.1756 3.72464 5 4.16667 5L9.16667 5Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"></path><path stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M5 11.6665C5 13.8302 5.70178 15.9355 7 17.6665C7.26522 18.0201 7.66005 18.2539 8.09763 18.3164C8.53522 18.3789 8.97971 18.2651 9.33333 17.9998C9.68695 17.7346 9.92074 17.3398 9.98325 16.9022C10.0458 16.4646 9.93188 16.0201 9.66667 15.6665C8.80119 14.5125 8.33333 13.109 8.33333 11.6665"></path><path stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" d="M6.66669 5L6.66669 11.6667"></path></svg>
<span>立即投递</span>
</button>
</div>
<div class="home-feature__visual home-feature__visual--referral">
<div class="referral-grid">
<div class="referral-card" v-for="(ref, i) in referralCards" :key="i" :class="{ 'referral-card--offset': i % 2 === 1 }">
<div class="referral-card__icon" :style="{ background: ref.color }">{{ ref.letter }}</div>
<p class="referral-card__company">{{ ref.company }}</p>
<p class="referral-card__role">{{ ref.role }}</p>
<span class="referral-card__code">内推码: {{ ref.code }}</span>
</div>
</div>
</div>
</div>
</section>
<!-- Feature 5: AI求职助手 -->
<section class="home-feature">
<div class="home-feature__inner">
<div class="home-feature__text">
<h2>24h全天候<br/>AI求职助手</h2>
<p>随时提供求职指导从岗位筛选到面试技巧你的专属职业规划顾问</p>
<button class="home-feature__btn" @click="router.push('/jobs')">
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 20 20" fill="none"><path d="M18.3333 14.1667C18.3333 14.6087 18.1577 15.0326 17.8452 15.3452C17.5326 15.6577 17.1087 15.8333 16.6667 15.8333L5.69 15.8333C5.24801 15.8334 4.82416 16.0091 4.51167 16.3217L2.67667 18.1567C2.59393 18.2394 2.48851 18.2957 2.37375 18.3186C2.25898 18.3414 2.14003 18.3297 2.03193 18.2849C1.92382 18.2401 1.83142 18.1643 1.76641 18.067C1.7014 17.9697 1.66669 17.8553 1.66667 17.7383L1.66667 4.16667C1.66667 3.72464 1.84227 3.30072 2.15483 2.98816C2.46739 2.67559 2.89131 2.5 3.33334 2.5L16.6667 2.5C17.1087 2.5 17.5326 2.67559 17.8452 2.98816C18.1577 3.30072 18.3333 3.72464 18.3333 4.16667L18.3333 14.1667Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"></path></svg>
<span>立即咨询</span>
</button>
</div>
<div class="home-feature__visual home-feature__visual--ai">
<div class="feature-ai-chat">
<div class="ai-bubble ai-bubble--bot">👋 哈喽我是你的 AI 职业导师正在为你分析即将到来的面试流程</div>
<div class="ai-bubble ai-bubble--user">能帮我模拟一下明天的腾讯面试吗</div>
<div class="ai-bubble ai-bubble--bot">
<p class="ai-bubble__title">准备就绪面试官画像已生成</p>
<div class="ai-bubble__tags">
<span>项目深度与工程化能力</span>
<span>计算机网络与操作系统</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 用户评价区 Testimonials Section -->
<section class="home-testimonials">
<div class="home-testimonials__orb home-testimonials__orb--left"></div>
<div class="home-testimonials__orb home-testimonials__orb--right"></div>
<div class="home-testimonials__container">
<div class="home-testimonials__header">
<h2>万千毕业生的信赖之选</h2>
<p>真实求职反馈见证每一次从迷茫到上岸</p>
</div>
<div class="home-testimonials__cards">
<div class="testimonial-card" v-for="(t, i) in testimonials" :key="i">
<p class="testimonial-card__text">{{ t.text }}</p>
<div class="dflex aliite-e">
<div class="testimonial-card__author">
<img class="testimonial-card__avatar" :src="t.avatar" alt="用户头像" />
<div class="testimonial-card__info">
<p class="testimonial-card__name">{{ t.name }}</p>
<p class="testimonial-card__school">{{ t.school }}</p>
</div>
</div>
<img class="w48 h48" src="@/assets/images/home/blue-icon-768.png" alt="">
</div>
</div>
</div>
<!-- 创始人引言卡片 -->
<div class="home-testimonials__founder">
<svg class="home-testimonials__founder-decor" width="455" height="455" viewBox="0 0 455 455" fill="none">
<circle cx="227.5" cy="227.5" r="189.5" stroke="#fff" stroke-width="20" opacity="0.08"/>
<path d="M240 190 L318 224 Q328 227.5 318 231 L240 265 Q227.5 270 215 265 L137 231 Q127 227.5 137 224 L215 190 Q227.5 185 240 190 Z" stroke="#fff" stroke-width="20" fill="none" opacity="0.08" transform="rotate(-45 227.5 227.5)"/>
</svg>
<div class="home-testimonials__founder-content">
<img class="home-testimonials__founder-img" :src="avatarImg" alt="创始人头像" />
<div class="home-testimonials__founder-text">
<blockquote>"很多大学生还在用传统方式找工作,海投、反复改简历,效率很低。Offer派利用AI技术让专业和岗位<span class="fw600">深度匹配</span>关联,配合<span class="fw600">自动化投递</span>,让校招求职流程更丝滑,更精准,节省了80%的繁琐过程,拿到更多Offer。"</blockquote>
<cite>
<span class="cite-name">{{ founderQuotes[founderIndex].name }}</span>
<span class="cite-role">{{ founderQuotes[founderIndex].role }}</span>
</cite>
</div>
</div>
</div>
</div>
</section>
<!-- FAQ Section -->
<section class="home-faq">
<div class="home-faq__inner">
<div class="home-faq__header">
<h2>常见问题</h2>
<p>关于平台使用隐私安全与岗位来源</p>
</div>
<div class="home-faq__list">
<div
v-for="(faq, i) in faqs"
:key="i"
class="faq-item"
:class="{ 'faq-item--open': faqOpen === i }"
>
<div class="faq-item__header" @click="faqOpen === i ? null : (faqOpen = i)">
<span class="faq-item__question">{{ faq.q }}</span>
<div class="faq-item__icon" @click.stop="faqOpen = faqOpen === i ? -1 : i">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<template v-if="faqOpen === i">
<line x1="7" y1="7" x2="17" y2="17" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<line x1="17" y1="7" x2="7" y2="17" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</template>
<template v-else>
<line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<line x1="12" y1="5" x2="12" y2="19" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</template>
</svg>
</div>
</div>
<div class="faq-item__answer" v-show="faqOpen === i">
<template v-if="i === faqs.length - 1">
<div class="faq-item__feedback">
<textarea
v-model="faqFeedbackText"
class="faq-item__textarea"
placeholder="请输入你的问题"
rows="4"
></textarea>
<button class="faq-item__submit" @click.stop="submitFaqFeedback">提交</button>
</div>
</template>
<template v-else>
<p>{{ faq.a }}</p>
</template>
</div>
</div>
</div>
</div>
</section>
<!-- Job Search Section -->
<section class="home-job-search">
<div class="home-job-search__inner">
<h3>一键找到最适合你的工作</h3>
<div class="home-job-search__filters">
<IndustrySelector
:industryIds="homeIndustryIds"
:level="2"
:maxSelect="3"
:allowParentSelect="false"
:allowParentSelectClick="true"
:triggerStyle="filterTriggerStyle"
:displayStyle="filterDisplayStyle"
@update:industryIds="onHomeIndustryChange"
/>
<RegionSelector
:regionCodes="homeRegionCodes"
:level="2"
:maxSelect="3"
:triggerStyle="filterTriggerStyle"
:displayStyle="filterDisplayStyle"
@update:regionCodes="onHomeRegionChange"
/>
<JobCategorySelector
:categoryIds="homeCategoryIds"
:level="3"
:maxSelect="3"
:allowParentSelect="false"
:allowParentSelectClick="true"
:triggerStyle="filterTriggerStyle"
:displayStyle="filterDisplayStyle"
@update:categoryIds="onHomeCategoryChange"
/>
<button class="filter-btn" @click="goSearchJobs">立即匹配</button>
</div>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import avatarImg from '@/assets/images/home/avatar-temporary.png'
import studentAvatar0 from '@/assets/images/home/student-avatar-0.png'
import studentAvatar1 from '@/assets/images/home/student-avatar-1.png'
import studentAvatar2 from '@/assets/images/home/student-avatar-2.png'
import IndustrySelector from '@/components/tools/IndustrySelector.vue'
import RegionSelector from '@/components/tools/RegionSelector.vue'
import JobCategorySelector from '@/components/tools/JobCategorySelector.vue'
import { getDailyJobCount } from '@/utils/dailyJobCount'
/** 路由实例 */
const router = useRouter()
/** Vuex 状态管理实例 */
const store = useStore()
/** 今日更新岗位数量 */
const dailyJobCount = getDailyJobCount()
/** 当前展开的 FAQ 索引 */
const faqOpen = ref(-1)
/** 创始人引言当前索引 */
const founderIndex = ref(0)
/** 创始人引言数据 */
const founderQuotes = [
{ text: '"很多大学生还在用传统方式找工作,海投、反复改简历,效率很低。Offer派利用AI技术让专业和岗位深度匹配关联,配合自动化投递,让校招求职流程更丝滑,更精准,节省了80%的繁琐过程,拿到更多Offer。"', name: 'TK', role: 'Offer派创始人' },
]
/** 岗位滚动展示数据 */
const tickerJobs = [
{ company: '字节跳动', time: '15分钟前', title: '高级前端开发工程师' },
{ company: '腾讯', time: '30分钟前', title: '后端开发工程师' },
{ company: '阿里巴巴', time: '1小时前', title: '产品经理' },
{ company: '华为', time: '2小时前', title: '算法工程师' },
{ company: '京东', time: '3小时前', title: '运营管培生' },
]
/** 内推人脉卡片数据 */
const referralCards = [
{ letter: 'b', company: '字节跳动', role: '前端工程师', code: 'r7u9xx', color: '#2563eb' },
{ letter: 'a', company: '阿里巴巴', role: '运营管培生', code: 'alicc2', color: '#ea580c' },
{ letter: 't', company: '腾讯', role: '后端开发工程师', code: 'tx9901', color: '#111827' },
{ letter: 'h', company: '华为', role: '通用软件研究员', code: 'hwl92', color: '#dc2626' },
]
/** 用户评价数据 */
const testimonials = [
{ text: '"Offer派求职体验特别好!岗位信息丰富、质量高,AI岗位匹配也很精准,求职助手给的面试建议很专业,一周就拿到了offer!"', name: '李同学', school: '西安交通大学', avatar: studentAvatar0 },
{ text: '"界面设计简洁友好,用起来很舒适。AI助手帮我理清了方向,让迷茫的我建立了自信,还能帮我优化简历,真的很nice~"', name: '王同学', school: '北京大学', avatar: studentAvatar1 },
{ text: '"满分!特别是网申自动填写,不仅可以一键填写,还能快速定制简历内容,完美匹配岗位要求,毕设求职两不误,真心推荐。"', name: '张同学', school: '中国人民大学', avatar: studentAvatar2 },
]
/** 用户反馈输入内容 */
const faqFeedbackText = ref('')
/** 提交用户反馈 */
function submitFaqFeedback() {
if (!faqFeedbackText.value.trim()) return
faqFeedbackText.value = ''
faqOpen.value = -1
}
/** 常见问题列表 */
const faqs = [
{ q: '这个平台与其他求职平台有什么不同?', a: '不同于BOSS直聘等传统平台的"手动式"求职,Offer派如同一位经验丰富的职业导师,通过AI能力搭建智能工具,自动处理简历优化、岗位匹配、申请投递等环节,大幅度提升你的求职效率。' },
{ q: '平台如何保障我的个人信息安全?', a: 'Offer派高度重视你的隐私安全。未经你明确同意,我们绝不会将你的个人信息提供给任何第三方。你的所有数据仅用于在平台内为你提供AI智能岗位匹配、简历优化等个性化求职服务。' },
{ q: '平台可以免费使用吗?', a: 'Offer派为所有用户免费开放3天会员权益,帮你省心省力找工作。3天后,依然可以免费使用各项功能,但有使用次数限制。' },
{ q: '平台的岗位来源是什么?', a: 'Offer派的岗位来自上万家企业的官方招聘网站,同时聚合了各大主流平台的最新职位——你不需要在多个网站之间来回切换,就能一站式搜罗全网高质量的工作机会。此外,我们会持续筛查每一则新发布的职位,及时剔除虚假信息,帮你避"坑",为你提供真实靠谱的岗位信息。' },
{ q: '还有其他问题?', a: '' },
]
// ==================== ====================
/** 首页选中的行业 ID 数组 */
const homeIndustryIds = computed<number[]>(() => store.state.jobIntention.industryIds || [])
/** 首页选中的地区编码数组 */
const homeRegionCodes = computed<string[]>(() => store.state.jobIntention.regionCodes || [])
/** 首页选中的岗位 ID 数组 */
const homeCategoryIds = computed<number[]>(() => store.state.jobIntention.categoryIds || [])
/** 选择器触发按钮样式 */
const filterTriggerStyle = {
width: '2.80rem',
'max-width': 'none',
height: '0.56rem',
'border-radius': '0.16rem',
border: '1px solid #e5e7eb',
padding: '0 0.2rem',
background: '#F3F4F5',
'justify-content': 'space-between',
}
/** 选择器显示文字样式 */
const filterDisplayStyle = {
'font-size': '0.16rem',
'line-height': '0.16rem',
color: 'rgba(0, 0, 0, 0.45)',
'max-width': '1.8rem',
}
/** 行业选择变更回调 */
function onHomeIndustryChange(ids: number[]) {
store.dispatch('saveJobIntention', { ...store.state.jobIntention, industryIds: ids })
}
/** 地区选择变更回调 */
function onHomeRegionChange(codes: string[]) {
store.dispatch('saveJobIntention', { ...store.state.jobIntention, regionCodes: codes })
}
/** 岗位选择变更回调 */
function onHomeCategoryChange(ids: number[]) {
store.dispatch('saveJobIntention', { ...store.state.jobIntention, categoryIds: ids })
}
/** 点击搜索职位 */
function goSearchJobs() {
router.push('/jobs')
}
</script>
+243
View File
@@ -0,0 +1,243 @@
<template>
<!-- 岗位雷达Tab内容 -->
<div class="home-job-radar">
<!-- 第一部分全网优质岗位实时精准匹配 -->
<section class="home-job-radar__section">
<div class="home-job-radar__header">
<h2 class="home-job-radar__title">全网优质岗位实时精准匹配</h2>
<p class="home-job-radar__subtitle">实时汇聚全网企业名额信息, 综合求职意向与履历适配, 让每一次投递都是精准匹配, 更高效</p>
</div>
<!-- 筛选面板 -->
<div class="home-job-radar__filters">
<!-- 行业筛选行 -->
<div class="home-job-radar__filter-row">
<span class="home-job-radar__filter-label">行业</span>
<div class="home-job-radar__filter-chips">
<span
class="home-job-radar__chip"
:class="{ 'home-job-radar__chip--active': selectedIndustryId === null }"
@click="selectIndustry(null)"
>全部</span>
<span
v-for="item in hotIndustries"
:key="'ind-' + item.id"
class="home-job-radar__chip"
:class="{ 'home-job-radar__chip--active': selectedIndustryId === item.id }"
@click="selectIndustry(item.id)"
>{{ item.name }}</span>
</div>
</div>
<!-- 城市筛选行 -->
<div class="home-job-radar__filter-row">
<span class="home-job-radar__filter-label">城市</span>
<div class="home-job-radar__filter-chips">
<span
class="home-job-radar__chip"
:class="{ 'home-job-radar__chip--active': selectedCityCode === null }"
@click="selectCity(null)"
>全部</span>
<span
v-for="item in hotCities"
:key="'city-' + item.code"
class="home-job-radar__chip"
:class="{ 'home-job-radar__chip--active': selectedCityCode === item.code }"
@click="selectCity(item.code)"
>{{ item.name }}</span>
</div>
</div>
<!-- 岗位筛选行 -->
<div class="home-job-radar__filter-row">
<span class="home-job-radar__filter-label">岗位</span>
<div class="home-job-radar__filter-chips">
<span
class="home-job-radar__chip"
:class="{ 'home-job-radar__chip--active': selectedCategoryId === null }"
@click="selectCategory(null)"
>全部</span>
<span
v-for="item in hotCategories"
:key="'cat-' + item.id"
class="home-job-radar__chip"
:class="{ 'home-job-radar__chip--active': selectedCategoryId === item.id }"
@click="selectCategory(item.id)"
>{{ item.name }}</span>
</div>
</div>
</div>
<!-- 岗位列表网格 -->
<div class="home-job-radar__grid">
<div
v-for="(job, index) in jobList"
:key="index"
class="home-job-radar__card"
@click="goToDetail(job)"
>
<!-- 上半部分岗位标题 + 标签 -->
<div class="home-job-radar__card-top">
<div class="home-job-radar__card-title">{{ job.title }}</div>
<div class="home-job-radar__card-tags">
<span v-if="job.regionName" class="home-job-radar__card-tag">{{ job.regionName }}</span>
<span v-if="formatEmploymentType(job.recruitCategory)" class="home-job-radar__card-tag">{{ formatEmploymentType(job.recruitCategory) }}</span>
<span v-if="formatEducation(job.education)" class="home-job-radar__card-tag">{{ formatEducation(job.education) }}</span>
</div>
</div>
<!-- 下半部分公司信息 + 发布时间 -->
<div class="home-job-radar__card-bottom">
<div class="home-job-radar__card-company">
<img v-if="job.companyLogoUrl" class="home-job-radar__card-logo" :src="job.companyLogoUrl" alt="" />
<div v-else class="home-job-radar__card-logo home-job-radar__card-logo--default">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none"><rect width="24" height="24" rx="12" fill="#EDF9FA"/><g clip-path="url(#clogo2)"><path d="M5.99998 16.6667H6.66665V8.24001C6.66667 8.0301 6.73274 7.82552 6.8555 7.65526C6.97827 7.48499 7.15149 7.35767 7.35065 7.29134L12.684 5.51401C12.8343 5.46395 12.9943 5.45031 13.1509 5.47423C13.3076 5.49815 13.4562 5.55893 13.5848 5.65157C13.7133 5.74421 13.8179 5.86606 13.8901 6.00708C13.9623 6.1481 14 6.30425 14 6.46268V16.6667H14.6666V10.66C14.6667 10.6107 14.6776 10.562 14.6987 10.5175C14.7199 10.4729 14.7506 10.4336 14.7887 10.4024C14.8268 10.3711 14.8714 10.3487 14.9193 10.3368C14.9671 10.3249 15.017 10.3237 15.0653 10.3333L16.5293 10.6267C16.7559 10.672 16.9599 10.7944 17.1064 10.973C17.253 11.1517 17.3332 11.3756 17.3333 11.6067V16.6667H18C18.1768 16.6667 18.3464 16.7369 18.4714 16.8619C18.5964 16.987 18.6666 17.1565 18.6666 17.3333C18.6666 17.5102 18.5964 17.6797 18.4714 17.8047C18.3464 17.9298 18.1768 18 18 18H5.99998C5.82317 18 5.6536 17.9298 5.52858 17.8047C5.40355 17.6797 5.33331 17.5102 5.33331 17.3333C5.33331 17.1565 5.40355 16.987 5.52858 16.8619C5.6536 16.7369 5.82317 16.6667 5.99998 16.6667Z" fill="#52CAD1"/></g><defs><clipPath id="clogo2"><rect width="16" height="16" fill="white" transform="translate(4 4)"/></clipPath></defs></svg>
</div>
<span class="home-job-radar__card-company-name">{{ job.companyShortName || job.companyName }}</span>
<span v-if="job.companyType" class="home-job-radar__card-company-type">{{ job.companyType }}</span>
</div>
<span class="home-job-radar__card-time">{{ formatExpireAt(job.expireAt) }}</span>
</div>
</div>
</div>
<!-- 加载中 -->
<div v-if="loading" class="home-job-radar__loading">
<div class="home-job-radar__spinner"></div>
<span>加载中...</span>
</div>
<!-- 查看更多按钮 -->
<div v-if="!loading && jobList.length > 0" class="home-job-radar__more">
<button class="home-job-radar__more-btn" @click="router.push('/jobs')">查看更多</button>
</div>
</section>
<!-- 第二部分CTA区域 -->
<section class="home-cta">
<div class="home-cta__inner">
<h2>让Offer派带你找到<br/>人生第一份好工作</h2>
<button class="home-cta__btn" @click="router.push('/jobs')">免费体验</button>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { useRouter } from 'vue-router'
import { fetchJobList, fetchHotIndustries, fetchHotCategories, fetchHotCities } from '@/api/jobs'
import type { JobListItem, HotIndustryItem, HotCategoryItem, HotCityItem } from '@/api/jobs'
import { formatEmploymentType, formatEducation } from '@/stores/index'
/** 路由实例 */
const router = useRouter()
// ==================== ====================
/** 热门行业列表 */
const hotIndustries = ref<HotIndustryItem[]>([])
/** 热门城市列表 */
const hotCities = ref<HotCityItem[]>([])
/** 热门岗位列表 */
const hotCategories = ref<HotCategoryItem[]>([])
/** 当前选中的行业IDnull=全部) */
const selectedIndustryId = ref<number | null>(null)
/** 当前选中的城市编码(null=全部) */
const selectedCityCode = ref<string | null>(null)
/** 当前选中的岗位IDnull=全部) */
const selectedCategoryId = ref<number | null>(null)
// ==================== ====================
/** 岗位列表数据 */
const jobList = ref<JobListItem[]>([])
/** 加载状态 */
const loading = ref(false)
/** 选择行业 */
function selectIndustry(id: number | null) {
selectedIndustryId.value = id
}
/** 选择城市 */
function selectCity(code: string | null) {
selectedCityCode.value = code
}
/** 选择岗位分类 */
function selectCategory(id: number | null) {
selectedCategoryId.value = id
}
/** 加载岗位列表 */
async function loadJobList() {
loading.value = true
try {
const params: any = {
pageNum: 1,
pageSize: 30,
}
if (selectedIndustryId.value !== null) {
params.industryIds = [selectedIndustryId.value]
}
if (selectedCityCode.value !== null) {
params.regionCodes = [selectedCityCode.value]
}
if (selectedCategoryId.value !== null) {
params.categoryIds = [selectedCategoryId.value]
}
const res = await fetchJobList(params)
if (res.code === '0' && res.data) {
jobList.value = res.data.list || []
}
} catch (e) {
console.error('加载岗位列表失败', e)
} finally {
loading.value = false
}
}
/** 格式化发布时间 */
function formatExpireAt(expireAt?: string | null): string {
if (!expireAt) return ''
try {
const date = new Date(expireAt)
if (isNaN(date.getTime())) return ''
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${month}-${day}更新`
} catch {
return ''
}
}
/** 跳转到岗位详情 */
function goToDetail(job: JobListItem) {
router.push({ path: `/jobs/${job.id}`, query: { from: 'home' } })
}
/** 加载热门筛选数据 */
async function loadHotFilters() {
try {
const [indRes, cityRes, catRes] = await Promise.all([
fetchHotIndustries(),
fetchHotCities(),
fetchHotCategories(),
])
if (indRes.code === '0' && indRes.data) hotIndustries.value = indRes.data
if (cityRes.code === '0' && cityRes.data) hotCities.value = cityRes.data
if (catRes.code === '0' && catRes.data) hotCategories.value = catRes.data
} catch (e) {
console.error('加载热门筛选数据失败', e)
}
}
//
watch([selectedIndustryId, selectedCityCode, selectedCategoryId], () => {
loadJobList()
})
onMounted(() => {
loadHotFilters()
loadJobList()
})
</script>
+324
View File
@@ -0,0 +1,324 @@
<template>
<!-- 求职辅导Tab内容 -->
<div class="home-mentor">
<!-- 第一部分找到真正懂行业的求职导师 -->
<section class="home-mentor__section">
<div class="home-mentor__header">
<h2 class="home-mentor__title">找到真正懂行业的求职导师</h2>
<p class="home-mentor__subtitle">连接名企在职导师获取来自真实职场的一对一求职指导与经验分享</p>
</div>
<!-- 行业分类Tab -->
<div class="home-mentor__tabs">
<span
v-for="(tab, index) in industryTabs"
:key="index"
class="home-mentor__tab"
:class="{ 'home-mentor__tab--active': activeTab === index }"
@click="handleTabChange(index)"
>{{ tab }}</span>
</div>
<!-- 导师列表滚动容器 -->
<div ref="listWrapRef" class="home-mentor__list-wrap scrollbar-thin" @scroll="onListScroll">
<!-- 导师列表 -->
<div class="home-mentor__list">
<div
v-for="mentor in mentorList"
:key="mentor.id"
class="home-mentor__card"
@click="goToDetail(mentor)"
>
<!-- 左侧头像 + 基本信息 -->
<div class="home-mentor__card-left">
<!-- 头像 -->
<div class="home-mentor__avatar-wrap">
<img
v-if="mentor.picture"
:src="toOssUrl(mentor.picture)"
class="home-mentor__avatar"
:alt="mentor.platformName"
/>
<div v-else class="home-mentor__avatar-placeholder">
{{ mentor.platformName?.charAt(0) || '师' }}
</div>
</div>
<!-- 导师基本信息 -->
<div class="home-mentor__card-info">
<div class="home-mentor__card-name-row">
<span class="home-mentor__card-name">{{ mentor.platformName }}</span>
<span v-if="mentor.agencyName" class="home-mentor__card-meta">
<img v-if="mentor.agencyLogo" :src="toOssUrl(mentor.agencyLogo)" class="home-mentor__card-agency-logo" alt="" />
{{ mentor.agencyName }}<template v-if="mentor.position">{{ mentor.position }}</template>
</span>
</div>
<!-- 标签列表 -->
<div class="home-mentor__card-labels" v-if="mentor.labels && mentor.labels.length">
<span
v-for="(label, li) in mentor.labels.slice(0, 6)"
:key="li"
class="home-mentor__card-label"
>{{ label }}</span>
</div>
</div>
</div>
<!-- 右侧话题 + 查看详情 -->
<div class="home-mentor__card-right">
<!-- 话题列表 -->
<div class="home-mentor__card-topics">
<div
v-for="(title, ti) in mentor.titles.slice(0, 3)"
:key="ti"
class="home-mentor__card-topic"
><span class="color-accent">#</span> {{ title }}</div>
</div>
<!-- 查看详情按钮 -->
<button class="home-mentor__detail-btn">
<span>查看详情</span>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.51612 4.07575C7.38279 3.94242 7.31879 3.78686 7.32412 3.60908C7.32945 3.43131 7.39901 3.27575 7.53278 3.14241C7.66612 3.02019 7.82167 2.95619 7.99946 2.95041C8.17723 2.94464 8.33278 3.00864 8.46612 3.14241L12.8661 7.54241C12.9328 7.60908 12.9801 7.6813 13.0081 7.75908C13.0361 7.83686 13.0497 7.92019 13.0488 8.00908C13.0479 8.09797 13.0339 8.18131 13.0068 8.25908C12.9797 8.33686 12.9326 8.40908 12.8654 8.47575L8.46545 12.8757C8.34323 12.998 8.19034 13.0591 8.00679 13.0591C7.82323 13.0591 7.66501 12.998 7.53212 12.8757C7.39879 12.7424 7.33212 12.584 7.33212 12.4004C7.33212 12.2169 7.39879 12.0586 7.53212 11.9257L10.7828 8.67575H3.33279C3.1439 8.67575 2.98546 8.61175 2.85745 8.48375C2.72945 8.35575 2.66567 8.19753 2.66612 8.00908C2.66656 7.82064 2.73056 7.6622 2.85812 7.53375C2.98568 7.40531 3.1439 7.34153 3.33279 7.34242L10.7828 7.34242L7.51612 4.07575Z" fill="currentColor"/>
</svg>
</button>
</div>
</div>
<!-- 加载中提示 -->
<div v-if="loading" class="home-mentor__loading">
<div class="home-mentor__spinner"></div>
<span>加载中...</span>
</div>
<!-- 加载更多中 -->
<div v-if="loadingMore" class="home-mentor__loading">
<div class="home-mentor__spinner"></div>
<span>加载中...</span>
</div>
<!-- 没有更多数据 -->
<div v-if="noMore && mentorList.length > 0" class="home-mentor__no-more">没有更多导师了</div>
<!-- 空列表 -->
<div v-if="!loading && mentorList.length === 0" class="home-mentor__empty">
暂无相关导师
</div>
</div>
</div>
</section>
<!-- 第二部分CTA区域 -->
<section class="home-cta">
<div class="home-cta__inner">
<h2>让Offer派带你找到<br/>人生第一份好工作</h2>
<button class="home-cta__btn" @click="goToMentorPage">免费体验</button>
</div>
</section>
</div>
</template>
<script setup lang="ts">
import { ref, computed, onMounted, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import { fetchMentorList } from '@/api/mentor'
import type { MentorItem, MentorListParams } from '@/api/mentor'
import { toOssUrl } from '@/utils/image'
/** 路由实例 */
const router = useRouter()
/** Vuex Store */
const store = useStore()
// ==================== Tab ====================
/** 行业 Tab 标签列表 */
const industryTabs = ['推荐', '互联网', '金融', '快消', '咨询', '央国企', '硬科技', '生涯规划', '名校导师', '心理咨询', '其他']
/** 当前选中的 Tab 索引 */
const activeTab = ref(0)
/** 生成 min~max 之间的整数数组 */
function createIntegerRange(min: number, max: number): number[] {
min = Math.floor(min)
max = Math.floor(max)
if (min > max) return []
const result: number[] = []
for (let i = min; i <= max; i++) result.push(i)
return result
}
/** 根据当前 Tab 计算 industryIds 参数 */
const industryIds = computed<number[]>(() => {
if (activeTab.value === 0) return []
if (activeTab.value === 1) return createIntegerRange(1000, 1016)
if (activeTab.value === 2) return createIntegerRange(1131, 1139)
if (activeTab.value === 3) return createIntegerRange(1038, 1047)
if (activeTab.value === 4) return createIntegerRange(1087, 1093)
if (activeTab.value === 5) return []
if (activeTab.value === 6) return [
1012, 1014, 1017, 1018, 1019, 1020, 1021, 1022, 1023,
1074, 1078, 1079, 1084, 1085,
1095, 1097, 1098,
1119, 1120, 1121, 1122, 1123, 1124, 1125, 1126, 1127, 1128, 1129, 1130,
]
if (activeTab.value === 7) return []
if (activeTab.value === 8) return []
if (activeTab.value === 9) return []
if (activeTab.value === 10) {
const all = createIntegerRange(1000, 1143)
const used = new Set([
...createIntegerRange(1000, 1016),
...createIntegerRange(1131, 1139),
...createIntegerRange(1038, 1047),
...createIntegerRange(1087, 1093),
1012, 1014, 1017, 1018, 1019, 1020, 1021, 1022, 1023,
1074, 1078, 1079, 1084, 1085,
1095, 1097, 1098,
1119, 1120, 1121, 1122, 1123, 1124, 1125, 1126, 1127, 1128, 1129, 1130,
])
return all.filter(id => !used.has(id))
}
return []
})
/** 根据当前 Tab 计算机构类型 type 参数 */
const mentorType = computed<string | null>(() => {
return activeTab.value === 5 ? '央国企' : null
})
/** 根据当前 Tab 计算身份类型 identityType 参数 */
const identityType = computed<number | null>(() => {
if (activeTab.value === 5) return 0
if (activeTab.value === 7) return 1
if (activeTab.value === 8) return 4
if (activeTab.value === 9) return 2
return null
})
// ==================== ====================
/** 导师列表数据 */
const mentorList = ref<MentorItem[]>([])
/** 加载状态(首次加载) */
const loading = ref(false)
/** 加载更多状态 */
const loadingMore = ref(false)
/** 当前页码 */
const pageNum = ref(1)
/** 每页条数 */
const pageSize = ref(20)
/** 是否没有更多数据 */
const noMore = ref(false)
/** 列表滚动容器 ref */
const listWrapRef = ref<HTMLElement | null>(null)
/** 构建接口请求参数 */
function buildParams(): MentorListParams {
const params: MentorListParams = {
page: pageNum.value,
pageSize: pageSize.value,
sort: 0,
}
if (industryIds.value.length) params.industryIds = industryIds.value
if (mentorType.value !== null) params.type = mentorType.value
if (identityType.value !== null) params.identityType = identityType.value
return params
}
/** 加载导师列表(首次加载) */
async function loadMentors() {
loading.value = true
pageNum.value = 1
noMore.value = false
mentorList.value = []
try {
const res: any = await fetchMentorList(buildParams())
const pageData = res?.data ?? res
const records = pageData?.records ?? []
mentorList.value = records
if (records.length < pageSize.value) {
noMore.value = true
}
} catch (e) {
console.error('[首页导师列表] 加载失败', e)
} finally {
loading.value = false
nextTick(() => { autoFillIfNeeded() })
}
}
/** 加载下一页 */
async function loadNextPage() {
if (loadingMore.value || noMore.value) return
loadingMore.value = true
pageNum.value++
try {
const res: any = await fetchMentorList(buildParams())
const pageData = res?.data ?? res
const records = pageData?.records ?? []
if (records.length === 0) {
noMore.value = true
pageNum.value--
} else {
mentorList.value.push(...records)
if (records.length < pageSize.value) {
noMore.value = true
}
}
} catch (e) {
pageNum.value--
console.error('[首页导师列表] 加载下一页失败', e)
} finally {
loadingMore.value = false
nextTick(() => { autoFillIfNeeded() })
}
}
/** 自动填满容器:内容不足以滚动时自动加载下一页 */
function autoFillIfNeeded() {
const el = listWrapRef.value
if (!el) return
if (el.scrollHeight <= el.clientHeight && !noMore.value && !loadingMore.value && !loading.value) {
loadNextPage()
}
}
/** 列表滚动事件 — 触底自动加载 */
function onListScroll() {
const el = listWrapRef.value
if (!el) return
if (el.scrollHeight - el.scrollTop - el.clientHeight < 100) {
loadNextPage()
}
}
/** 切换行业 Tab */
function handleTabChange(index: number) {
if (activeTab.value === index) return
activeTab.value = index
loadMentors()
}
/** 点击查看详情 — 需要登录验证 */
function goToDetail(mentor: MentorItem) {
if (!store.state.isAuthenticated) {
store.dispatch('openLogin', `/mentor/${mentor.id}`)
return
}
router.push({ path: `/mentor/${mentor.id}`, query: { price: String(mentor.price), from: 'home' } })
}
/** 跳转导师页面 — 需要登录验证 */
function goToMentorPage() {
if (!store.state.isAuthenticated) {
store.dispatch('openLogin', '/mentor')
return
}
router.push('/mentor')
}
onMounted(() => {
loadMentors()
})
</script>
+2 -2
View File
@@ -409,11 +409,11 @@ const mainMenus = computed<MenuItem[]>(() => {
const showShareDialog = ref(false)
/** 是否为窄版导航(窗口宽度 < 1440px */
const isNarrow = ref(window.innerWidth < 1440)
const isNarrow = ref(window.innerWidth < 6440)
/** 监听窗口宽度变化,动态切换窄版/宽版 */
function handleNavResize() {
isNarrow.value = window.innerWidth < 1440
isNarrow.value = window.innerWidth < 6440
}
const showMessageDialog = ref(false)
const showFeedbackDialog = ref(false)
+1 -1
View File
@@ -97,7 +97,7 @@ const remAdaptPlugin: Plugin = {
docEl.style.setProperty('--app-height', '100vh')
docEl.style.setProperty('--chat-width', '3.6rem')
// 根据窗口宽度设置导航栏宽度CSS变量
const navWidth = window.innerWidth < 1440 ? '0.8rem' : '2.2rem'
const navWidth = window.innerWidth < 6440 ? '0.8rem' : '2.2rem'
docEl.style.setProperty('--nav-width', navWidth)
}
}
+104 -26
View File
@@ -349,7 +349,7 @@ import JobStatsOverview from '@/components/JobStatsOverview.vue'
import IndustrySelector from '@/components/tools/IndustrySelector.vue'
import JobCategorySelector from '@/components/tools/JobCategorySelector.vue'
import RegionSelector from '@/components/tools/RegionSelector.vue'
import { fetchAnnouncementList } from '@/api/announcement'
import { fetchAnnouncementList, fetchAnnouncementLastUpdateTime, fetchAnnouncementStatistics, fetchAnnouncementRecruitYearTags, fetchAnnouncementEducationOptions, fetchAnnouncementBatchOptions } from '@/api/announcement'
import type { AnnouncementListParams, AnnouncementItem } from '@/api/announcement'
import { fetchHotIndustries, fetchHotCategories, fetchHotCities } from '@/api/jobs'
import type { HotIndustryItem, HotCategoryItem, HotCityItem } from '@/api/jobs'
@@ -378,20 +378,41 @@ let filtersPanelObserver: ResizeObserver | null = null
// ==================== ====================
/** 当前日期MM.DD 格式) */
const statsCurrentDate = computed(() => {
const now = new Date()
return `${String(now.getMonth() + 1).padStart(2, '0')}.${String(now.getDate()).padStart(2, '0')}`
})
/** 最新更新时间MM.DD 格式,从后端获取 */
const statsCurrentDate = ref('--')
/** 今日新增公告数 */
const statsTodayCount = ref<string | number>('128')
const statsTodayCount = ref<string | number>('--')
/** 招聘中企业数 */
const statsTotalCompany = ref<string | number>('326')
/** 招聘中企业数(累计公告数) */
const statsTotalCompany = ref<string | number>('--')
/** 近7日截止数 */
const statsDeadline7days = ref<string | number>('42')
/** 近7日截止数(本月新增) */
const statsDeadline7days = ref<string | number>('--')
/** 加载统计卡片数据 */
async function loadStatsData() {
try {
//
const [timeRes, statsRes] = await Promise.all([
fetchAnnouncementLastUpdateTime(),
fetchAnnouncementStatistics(),
])
// MM.DD
if (timeRes.code === '0' && timeRes.data) {
const date = new Date(Number(timeRes.data))
statsCurrentDate.value = `${String(date.getMonth() + 1).padStart(2, '0')}.${String(date.getDate()).padStart(2, '0')}`
}
//
if (statsRes.code === '0' && statsRes.data) {
statsTodayCount.value = statsRes.data.dayCount ?? 0
statsTotalCompany.value = statsRes.data.totalCount ?? 0
statsDeadline7days.value = statsRes.data.monthCount ?? 0
}
} catch (e) {
console.error('加载统计数据失败', e)
}
}
// ==================== ====================
@@ -467,25 +488,56 @@ const showBatchDropdown = ref(false)
// ==================== ====================
/** 毕业年份选项 */
const recruitYearOptions = computed(() => {
const currentYear = new Date().getFullYear()
return [
{ label: '全部', value: 0 },
{ label: `${currentYear}`, value: currentYear },
{ label: `${currentYear + 1}`, value: currentYear + 1 },
{ label: `${currentYear + 2}`, value: currentYear + 2 },
]
})
/** 毕业年份选项(从后端接口获取) */
const recruitYearOptions = ref<{ label: string; value: number }[]>([{ label: '全部', value: 0 }])
/** 学历要求选项 */
const educationOptions = ['不限', '大专及以上', '本科及以上', '硕士及以上', '博士及以上']
/** 加载毕业年份标签 */
async function loadRecruitYearTags() {
try {
const res = await fetchAnnouncementRecruitYearTags()
if (res.code === '0' && res.data) {
recruitYearOptions.value = [
{ label: '全部', value: 0 },
...res.data.map((year: number) => ({ label: `${year}`, value: year })),
]
}
} catch (e) {
console.error('加载毕业年份标签失败', e)
}
}
/** 学历要求选项(从后端接口获取) */
const educationOptions = ref<string[]>(['不限'])
/** 加载学历筛选选项 */
async function loadEducationOptions() {
try {
const res = await fetchAnnouncementEducationOptions()
if (res.code === '0' && res.data) {
educationOptions.value = ['不限', ...res.data]
}
} catch (e) {
console.error('加载学历选项失败', e)
}
}
/** 公司类型选项 */
const companyTypeOptions = ['全部', '央企', '国企', '上市企业', '外资企业', '民营企业', '事业单位']
/** 招聘批次选项 */
const batchOptions = ['全部', '秋招正式批', '秋招提前批', '春招正式批', '春招补录', '暑期实习']
/** 招聘批次选项(从后端接口获取) */
const batchOptions = ref<string[]>(['全部'])
/** 加载招聘批次选项 */
async function loadBatchOptions() {
try {
const res = await fetchAnnouncementBatchOptions()
if (res.code === '0' && res.data) {
batchOptions.value = ['全部', ...res.data]
}
} catch (e) {
console.error('加载招聘批次选项失败', e)
}
}
/** 热门搜索标签 */
const hotSearchTags = ref(['系统集成', 'AI产品经理', '广告投放', 'AI开发', '大模型'])
@@ -806,7 +858,7 @@ const selectorDisplayStyle: Record<string, string> = {
const pageNum = ref(1)
/** 每页条数 */
const pageSize = ref(10)
const pageSize = ref(15)
/** 总记录数 */
const total = ref(0)
@@ -867,6 +919,8 @@ async function loadList() {
console.error('加载校招公告列表失败', e)
} finally {
loading.value = false
//
nextTick(() => { autoFillIfNeeded() })
}
}
@@ -892,6 +946,18 @@ async function loadNextPage() {
console.error('加载下一页失败', e)
} finally {
loadingMore.value = false
//
nextTick(() => { autoFillIfNeeded() })
}
}
/** 自动填满容器:当列表内容不足以产生滚动条且还有更多数据时,自动加载下一页 */
function autoFillIfNeeded() {
const el = listRef.value
if (!el) return
//
if (el.scrollHeight <= el.clientHeight && !noMore.value && !loadingMore.value && !loading.value) {
loadNextPage()
}
}
@@ -969,6 +1035,18 @@ onMounted(() => {
//
loadHotFilters()
//
loadStatsData()
//
loadRecruitYearTags()
//
loadEducationOptions()
//
loadBatchOptions()
//
nextTick(() => {
if (filtersPanelRef.value) {
+85 -24
View File
@@ -57,8 +57,8 @@
</div>
<div class="announcement-detail__company-tags">
<span v-if="detail.companyType" class="announcement-detail__tag announcement-detail__tag--type">{{ detail.companyType }}</span>
<span v-if="detail.industryNames && detail.industryNames.length" class="announcement-detail__tag announcement-detail__tag--gray">{{ detail.industryNames.join('/') }}</span>
<span v-if="companyScaleText" class="announcement-detail__tag announcement-detail__tag--gray">{{ companyScaleText }}</span>
<span v-if="detail.categoryNames && detail.categoryNames.length" class="announcement-detail__tag announcement-detail__tag--gray">{{ detail.categoryNames.join('/') }}</span>
<span v-if="detail.companyScale" class="announcement-detail__tag announcement-detail__tag--gray">{{ detail.companyScale}}</span>
</div>
</div>
</div>
@@ -159,10 +159,10 @@
<p class="announcement-detail__section-content">{{ detail.writtenExam }}</p>
</div>
<div v-if="detail.source" class="announcement-detail__section">
<!-- <div v-if="detail.source" class="announcement-detail__section">
<h3 class="announcement-detail__section-title">信息来源</h3>
<p class="announcement-detail__section-content">{{ detail.source }}</p>
</div>
</div> -->
</div>
<!-- 右侧边栏 -->
@@ -190,6 +190,8 @@
v-for="(job, idx) in otherJobs"
:key="'other-' + idx"
class="announcement-detail__job-item"
style="cursor: pointer;"
@click="router.push('/jobs/' + job.id)"
>
<div class="announcement-detail__job-item-main">
<span class="announcement-detail__job-item-title">{{ job.title }}</span>
@@ -231,6 +233,8 @@
v-for="(rec, idx) in recommendList"
:key="'rec-' + idx"
class="announcement-detail__rec-item"
style="cursor: pointer;"
@click="router.push('/announcement/' + rec.id)"
>
<img
v-if="rec.logoUrl"
@@ -263,11 +267,11 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue'
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import SideNav from '@/components/SideNav.vue'
import { fetchAnnouncementDetail } from '@/api/announcement'
import type { AnnouncementDetail } from '@/api/announcement'
import { fetchAnnouncementDetail, fetchAnnouncementRecommend } from '@/api/announcement'
import type { AnnouncementDetail, AnnouncementRecommendItem } from '@/api/announcement'
import { fetchJobList } from '@/api/jobs'
import type { JobListItem } from '@/api/jobs'
@@ -277,7 +281,7 @@ const router = useRouter()
const route = useRoute()
/** 当前公告ID(大数字符串,避免精度丢失) */
const announcementId = route.params.id as string
let announcementId = route.params.id as string
// ==================== ====================
@@ -293,6 +297,7 @@ const detail = reactive<AnnouncementDetail>({
companyType: '',
title: '',
companyIntro: '',
companyScale: '',
targetAudience: '',
majorRequire: '',
recruitPosition: '',
@@ -303,6 +308,8 @@ const detail = reactive<AnnouncementDetail>({
applyEndDesc: '',
inviteCode: '',
source: '',
announcementUrl: '',
applyUrl: '',
publishTime: null,
cleanStatus: 0,
status: 0,
@@ -319,8 +326,6 @@ const detail = reactive<AnnouncementDetail>({
// ==================== ====================
/** 公司规模文本 */
const companyScaleText = computed(() => '')
/** 发布日期格式化文本 */
const publishDateText = computed(() => {
@@ -363,6 +368,7 @@ const recruitCategoryMap: Record<number, string> = {
}
interface OtherJobItem {
id: string
title: string
city: string
type: string
@@ -398,6 +404,7 @@ function pickRandomJobs(list: JobListItem[]): OtherJobItem[] {
return picked.map(job => {
const style = getScoreStyle(job.matchScore ?? 0)
return {
id: job.id,
title: job.title,
city: job.regionName || '',
type: recruitCategoryMap[job.recruitCategory ?? 3] || '',
@@ -418,7 +425,7 @@ async function loadCompanyJobs() {
const res = await fetchJobList({
keyword: detail.companyName,
pageNum: 1,
pageSize: 100,
pageSize: 30,
})
if (res.code === '0' && res.data?.list) {
companyAllJobs.value = res.data.list
@@ -434,9 +441,10 @@ async function refreshOtherJobs() {
await loadCompanyJobs()
}
// ==================== ====================
// ==================== ====================
interface RecommendItem {
id: string
logoUrl: string
companyName: string
positions: string
@@ -444,22 +452,63 @@ interface RecommendItem {
cities: string
}
const recommendList = ref<RecommendItem[]>([
{ logoUrl: '', companyName: '泉峰科技', positions: '产品研发/工程与制造/质量与供应链/销售与市场...', industry: '机械/制造业', cities: '上海市, 南京市' },
{ logoUrl: '', companyName: '海天集团', positions: '技术类 产品类 设计类岗位', industry: 'IT/互联网/游戏', cities: '北京市, 杭州市' },
{ logoUrl: '', companyName: '陕西水务发展集团', positions: '水利水电工程施工设计岗 -A01, 高效节水灌溉设...', industry: '能源/化工/环保', cities: '宝鸡市, 咸阳市, 渭南市...' },
{ logoUrl: '', companyName: '陕西水务发展集团', positions: '水利水电工程施工设计岗 -A01, 高效节水灌溉设...', industry: '能源/化工/环保', cities: '宝鸡市, 咸阳市, 渭南市...' },
])
/** 推荐列表全量缓存(一次请求100条) */
const recommendAllList = ref<AnnouncementRecommendItem[]>([])
/** 换一批(校招推荐 */
function refreshRecommend() {
//
/** 当前展示的推荐列表(随机取4条 */
const recommendList = ref<RecommendItem[]>([])
/**
* 从全量推荐列表中随机选取最多4条转换为展示格式
*/
function pickRandomRecommend(list: AnnouncementRecommendItem[]): RecommendItem[] {
if (!list.length) return []
const shuffled = [...list].sort(() => Math.random() - 0.5)
const picked = shuffled.slice(0, 4)
return picked.map(item => ({
id: String(item.id),
logoUrl: item.logoUrl || '',
companyName: item.companyName || '',
positions: item.categoryNames?.join('/') || '',
industry: '',
cities: item.cityNames?.join(', ') || '',
}))
}
/**
* 加载校招推荐列表请求100条缓存随机展示4条
*/
async function loadRecommendList() {
if (!announcementId) return
try {
const res = await fetchAnnouncementRecommend({
announcementId: announcementId,
pageNum: 1,
pageSize: 100,
})
if (res.code === '0' && res.data?.list) {
recommendAllList.value = res.data.list
recommendList.value = pickRandomRecommend(recommendAllList.value)
}
} catch (e) {
console.error('加载校招推荐列表失败', e)
}
}
/** 换一批(校招推荐)—— 重新请求接口随机展示4条 */
async function refreshRecommend() {
await loadRecommendList()
}
// ==================== ====================
/** 返回列表页 */
function goBack() {
//
if (route.query.from === 'home') {
router.push('/announcement')
return
}
if (window.history.length > 1) {
router.back()
} else {
@@ -470,14 +519,14 @@ function goBack() {
/** 查看公告 */
function handleViewAnnouncement() {
if (detail.source) {
window.open(detail.source, '_blank')
window.open(detail.announcementUrl, '_blank')
}
}
/** 投递地址 */
function handleApplyUrl() {
if (detail.source) {
window.open(detail.source, '_blank')
window.open(detail.applyUrl, '_blank')
}
}
@@ -490,8 +539,9 @@ async function loadDetail() {
const res = await fetchAnnouncementDetail(announcementId)
if (res.code === '0' && res.data) {
Object.assign(detail, res.data)
//
//
loadCompanyJobs()
loadRecommendList()
}
} catch (e) {
console.error('加载校招公告详情失败', e)
@@ -503,4 +553,15 @@ async function loadDetail() {
onMounted(() => {
loadDetail()
})
//
watch(
() => route.params.id,
(newId) => {
if (newId && newId !== announcementId) {
announcementId = newId as string
loadDetail()
}
}
)
</script>
+70 -563
View File
@@ -1,450 +1,50 @@
<template>
<div class="home-page">
<!-- Hero 主视觉区包含顶部导航 -->
<section class="home-hero">
<!-- 底层背景色块 -->
<div class="home-hero__orb home-hero__orb--top"></div>
<div class="home-hero__orb home-hero__orb--bottom"></div>
<!-- 顶部导航栏 -->
<header class="home-nav mt0">
<div class="home-nav__inner" :class="{ 'home-nav__inner--scorlled': isScrolled }">
<div class="home-nav__logo">
<!-- 导航栏Logo图片 -->
<img src="@/assets/images/logo.png" alt="Offer派" class="home-nav__logo-img" />
</div>
<div class="dflex-end aliite-c">
<!-- 未登录时显示登录和立即加入按钮 -->
<div v-if="!store.state.isAuthenticated" class="fs14 color-1 mr40 fw600 cursor-po" @click="handleLoginClick">登录</div>
<button v-if="!store.state.isAuthenticated" class="home-nav__btn" @click="router.push('/jobs')">立即加入</button>
<!-- 已登录时显示进入平台按钮 -->
<button v-else class="home-nav__btn" @click="router.push('/jobs')">进入平台</button>
</div>
<!-- 顶部导航栏 -->
<header class="home-nav mt0">
<div class="home-nav__inner" :class="{ 'home-nav__inner--scorlled': isScrolled }">
<div class="home-nav__logo">
<!-- 导航栏Logo图片 -->
<img src="@/assets/images/logo.png" alt="Offer派" class="home-nav__logo-img" />
</div>
</header>
<div class="home-hero__inner">
<div class="home-hero__left">
<h1 class="home-hero__title">Offer派<br/><span class="dib"></span>收offer就是快</h1>
<p class="home-hero__desc">智能匹配职位自动填写申请量身定制简历推荐内部人脉<br/>不到1分钟统统搞定</p>
<button class="home-hero__cta" @click="router.push('/jobs')">免费体验</button>
</div>
<!-- 右侧视频展示区 -->
<div class="home-hero__right">
<!-- <div class="home-hero__right-top">-->
<!-- <div></div>-->
<!-- <div></div>-->
<!-- <div></div>-->
<!-- </div>-->
<!-- <video-->
<!-- class="home-hero__video"-->
<!-- src="https://jsxq-image-static.oss-cn-shenzhen.aliyuncs.com/aiJob/find/open-intention-video.mp4"-->
<!-- autoplay-->
<!-- loop-->
<!-- muted-->
<!-- playsinline-->
<!-- ></video>-->
<div>
<img class="w706 h452" src="@/assets/images/home/home-top.gif" alt="">
</div>
</div>
</div>
</section>
<!-- Stats Section -->
<section class="home-stats">
<div class="home-stats__inner">
<div class="home-stats__header">
<h2 class="fs38">让每一次求职努力<br/>都成为看得见的成果</h2>
<div class="home-stats__divider"></div>
</div>
<div class="home-stats__cards">
<article class="stat-card">
<div class="stat-card__num">第一</div>
<p class="stat-card__label">84.5%大学生求职首选</p>
</article>
<article class="stat-card">
<div class="stat-card__num">10</div>
<p class="stat-card__label">面试邀约率提升</p>
</article>
<article class="stat-card">
<div class="stat-card__num">82%</div>
<p class="stat-card__label">节约82%校招求职时间</p>
</article>
</div>
</div>
</section>
<!-- Jobs Showcase Section -->
<section class="home-jobs-showcase">
<!-- 背景色块 顶部小块 -->
<div class="home-jobs-showcase__orb home-jobs-showcase__orb--top"></div>
<!-- 背景色块 底部全宽渐变 -->
<div class="home-jobs-showcase__orb home-jobs-showcase__orb--bottom"></div>
<div class="home-jobs-showcase__inner">
<h2 class="fs38">海量优质校招岗位尽在Offer派</h2>
<p class="home-jobs-showcase__sub">实时汇集海量超10000+名校校招职位</p>
<div class="home-jobs-showcase__box prl25">
<div class="home-jobs-showcase__stats">
<div class="showcase-stat">
<div class="showcase-stat__num"><span><span class="accent">40</span><span class="accent"></span></span><span class="fs32">+</span></div>
<div class="showcase-stat__label">岗位总数</div>
</div>
<div class="showcase-stat">
<div class="showcase-stat__num"><span class="accent">{{ dailyJobCount }}</span><span class=""></span></div>
<div class="showcase-stat__label">今日更新</div>
</div>
</div>
<div class="home-jobs-showcase__scroll">
<!-- 无缝轮播两组相同内容拼接动画滚完第一组后无缝衔接 -->
<div class="ticker-track">
<div class="job-ticker" v-for="(job, i) in tickerJobs" :key="'a' + i">
<span class="job-ticker__company">{{ job.company }}·{{ job.time }}</span>
<span class="job-ticker__title">{{ job.title }}</span>
</div>
<div class="job-ticker" v-for="(job, i) in tickerJobs" :key="'b' + i">
<span class="job-ticker__company">{{ job.company }}·{{ job.time }}</span>
<span class="job-ticker__title">{{ job.title }}</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Feature 1: 个性化岗位匹配 -->
<section class="home-feature">
<div class="home-feature__inner">
<div class="home-feature__text">
<h2>个性化智能<br/>岗位匹配</h2>
<p>第一时间发现真正适合你的岗位精准匹配你的真<br/>实技能杜绝虚假信息</p>
<button class="home-feature__btn" @click="router.push('/jobs')">
<svg width="12" height="12" viewBox="0 0 14 23" fill="none"><circle cx="10" cy="10" r="8" stroke="currentColor" stroke-width="2"/><circle cx="10" cy="10" r="4" stroke="currentColor" stroke-width="2"/><circle cx="10" cy="10" r="1.5" fill="currentColor"/></svg>
<span>立即匹配</span>
</button>
</div>
<div class="home-feature__visual home-feature__visual--match">
<div class="feature-match-card">
<div class="feature-match-card__header">
<div class="feature-match-card__avatar"><img class="wp100 hp100" src="@/assets/images/home/alex-avatar.png" alt=""></div>
<div class="feature-match-card__info">
<h4>求职者 Alex</h4>
<div class="feature-match-card__tags">
<span>React</span><span>Tailwind</span>
</div>
</div>
<div class="feature-match-card__score">98%</div>
</div>
<div class="feature-match-card__bars">
<div class="bar bar--full"></div>
<div class="bar bar--3q"></div>
</div>
<div class="feature-match-card__result">为您匹配了 5 个高星职位</div>
</div>
</div>
</div>
</section>
<!-- Feature 2: 一键自动网申 -->
<section class="home-feature home-feature--reverse">
<div class="home-feature__inner">
<div class="home-feature__text">
<h2>校招一键<br/>自动网申</h2>
<p>每日向数百岗位一键投递覆盖各大企业网申系<br/>告别重复填写节省 82% 校招求职时间</p>
<button class="home-feature__btn" @click="router.push('/jobs')">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="13" height="13" viewBox="0 0 20 21" fill="none"><path d="M3.33331 11.6667C3.17561 11.6673 3.021 11.623 2.88743 11.5392C2.75387 11.4554 2.64683 11.3354 2.57876 11.1931C2.51069 11.0509 2.48438 10.8922 2.50289 10.7356C2.52139 10.579 2.58396 10.4309 2.68331 10.3084L10.9333 1.80839C10.9952 1.73696 11.0795 1.68868 11.1725 1.6715C11.2654 1.65431 11.3614 1.66923 11.4447 1.71381C11.5281 1.75838 11.5938 1.82997 11.6311 1.91681C11.6684 2.00365 11.675 2.10059 11.65 2.19172L10.05 7.20839C10.0028 7.33466 9.98695 7.47049 10.0038 7.60423C10.0206 7.73797 10.0697 7.86562 10.1467 7.97624C10.2237 8.08686 10.3265 8.17715 10.446 8.23936C10.5656 8.30156 10.6985 8.33383 10.8333 8.33339L16.6666 8.33339C16.8243 8.33285 16.9789 8.37707 17.1125 8.46091C17.2461 8.54474 17.3531 8.66476 17.4212 8.80701C17.4893 8.94926 17.5156 9.1079 17.4971 9.26451C17.4785 9.42111 17.416 9.56926 17.3166 9.69172L9.06664 18.1917C9.00476 18.2632 8.92042 18.3114 8.82749 18.3286C8.73455 18.3458 8.63854 18.3309 8.5552 18.2863C8.47186 18.2417 8.40615 18.1701 8.36886 18.0833C8.33157 17.9965 8.32491 17.8995 8.34997 17.8084L9.94997 12.7917C9.99715 12.6655 10.013 12.5296 9.99615 12.3959C9.9793 12.2621 9.93026 12.1345 9.85323 12.0239C9.77621 11.9132 9.6735 11.823 9.55391 11.7608C9.43433 11.6985 9.30144 11.6663 9.16664 11.6667L3.33331 11.6667Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" style="mix-blend-mode:normal"></path></svg>
<span>开启自动网申</span>
</button>
</div>
<div class="home-feature__visual home-feature__visual--apply">
<div class="feature-apply-card">
<div class="feature-apply-card__header">
<span class="feature-apply-card__status">正在执行投递程序...</span>
<div class="feature-apply-card__dots">
<span class="dot dot--red"></span>
<span class="dot dot--yellow"></span>
<span class="dot dot--green"></span>
</div>
</div>
<div class="feature-apply-card__list">
<div class="apply-item">
<div class="apply-item__icon"><img class="wp100 hp100" src="@/assets/images/home/right-icon-01.png" alt=""></div>
<div class="apply-item__info">
<p class="apply-item__title">腾讯科技 - 校招投递成功</p>
<p class="apply-item__sub">耗时 0.8s · 自动识别填写完毕</p>
</div>
</div>
<div class="apply-item apply-item--active">
<div class="apply-item__icon apply-item__icon--pulse"><img class="wp100 hp100" src="@/assets/images/home/load-icon-01.png" alt=""></div>
<div class="apply-item__info">
<p class="apply-item__title">京东校招 - 自动填写中...</p>
<p class="apply-item__sub">进度 65%</p>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Feature 3: 岗位定制简历 -->
<section class="home-feature">
<div class="home-feature__inner">
<div class="home-feature__text">
<h2>岗位定制简历</h2>
<p>10秒内生成针对特定岗位优化的专业简历通过<br/>ATS系统突出你的核心优势</p>
<button class="home-feature__btn" @click="router.push('/jobs')">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14" viewBox="0 0 20 22" fill="none"><path stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" style="mix-blend-mode:normal" d="M10.5492 18.3334L15 18.3334C15.442 18.3334 15.8659 18.1578 16.1785 17.8453C16.4911 17.5327 16.6667 17.1088 16.6667 16.6668L16.6667 6.66675C16.6673 6.40268 16.6157 6.14109 16.5147 5.89708C16.4137 5.65307 16.2654 5.43148 16.0783 5.24509L13.0883 2.25509C12.9019 2.06802 12.6803 1.91972 12.4363 1.81874C12.1923 1.71776 11.9307 1.66611 11.6667 1.66675L4.99999 1.66675C4.55797 1.66675 4.13404 1.84235 3.82148 2.15491C3.50892 2.46747 3.33333 2.89139 3.33333 3.33342L3.33333 11.1168"></path><path stroke="rgba(82, 202, 209, 1)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" style="mix-blend-mode:normal" d="M11.6667 1.66675L11.6667 5.83341C11.6667 6.05443 11.7545 6.26639 11.9107 6.42267C12.067 6.57895 12.279 6.66675 12.5 6.66675L16.6667 6.66675"></path><path d="M8.64834 10.5184C8.98019 10.1869 9.43015 10.0007 9.89922 10.001C10.3683 10.0012 10.8181 10.1878 11.1496 10.5196C11.4811 10.8515 11.6672 11.3014 11.667 11.7705C11.6668 12.2396 11.4802 12.6894 11.1483 13.0209L6.96667 17.1975C6.76865 17.3957 6.52389 17.5407 6.255 17.6192L3.86584 18.3167C3.79413 18.3376 3.71812 18.3389 3.64576 18.3203C3.5734 18.3018 3.50736 18.2642 3.45454 18.2113C3.40173 18.1585 3.36408 18.0925 3.34554 18.0201C3.327 17.9478 3.32826 17.8718 3.34917 17.8L4.04584 15.4092C4.12448 15.1406 4.2695 14.8962 4.4675 14.6984L8.64834 10.5184Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" style="mix-blend-mode:normal"></path></svg>
<span>优化我的简历</span>
</button>
</div>
<div class="home-feature__visual home-feature__visual--resume">
<div class="feature-resume-card">
<div class="feature-resume-card__doc--border">
<div class="feature-resume-card__doc">
<div class="doc-header">
<div class="doc-avatar"></div>
<div class="doc-lines">
<div class="doc-line doc-line--half"></div>
<div class="doc-line doc-line--third"></div>
</div>
</div>
<div class="doc-bars">
<div class="doc-bar doc-bar--1"></div>
<div class="doc-bar doc-bar--2"></div>
<div class="doc-bar doc-bar--3"></div>
</div>
</div>
</div>
<div class="feature-resume-card__badge">
<img class="w15 h15" src="@/assets/images/home/blue-shield-icon-01.png" alt="">
<span>ATS OPTIMIZED</span>
</div>
</div>
</div>
</div>
</section>
<!-- Feature 4: 内推人脉 -->
<section class="home-feature home-feature--reverse">
<div class="home-feature__inner">
<div class="home-feature__text">
<h2>名企内推<br/>人脉直通</h2>
<p>实时获取名企最新内推信息自动填写网申内推<br/>简历更快到达HR</p>
<button class="home-feature__btn" @click="router.push('/jobs')">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="13" height="13" viewBox="0 0 20 20" fill="none"><path d="M9.16667 5C11.7011 5.06529 14.1783 4.23959 16.1667 2.66667C16.2905 2.57381 16.4377 2.51727 16.5918 2.50337C16.746 2.48947 16.9009 2.51877 17.0393 2.58798C17.1778 2.65719 17.2942 2.76358 17.3755 2.89522C17.4569 3.02687 17.5 3.17857 17.5 3.33333L17.5 13.3333C17.5 13.4881 17.4569 13.6398 17.3755 13.7714C17.2942 13.9031 17.1778 14.0095 17.0393 14.0787C16.9009 14.1479 16.746 14.1772 16.5918 14.1633C16.4377 14.1494 16.2905 14.0929 16.1667 14C14.1783 12.4271 11.7011 11.6014 9.16667 11.6667L4.16667 11.6667C3.72464 11.6667 3.30072 11.4911 2.98816 11.1785C2.67559 10.866 2.5 10.442 2.5 10L2.5 6.66667C2.5 6.22464 2.67559 5.80072 2.98816 5.48816C3.30072 5.1756 3.72464 5 4.16667 5L9.16667 5Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" style="mix-blend-mode:normal"></path><path stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" style="mix-blend-mode:normal" d="M5 11.6665C5 13.8302 5.70178 15.9355 7 17.6665C7.26522 18.0201 7.66005 18.2539 8.09763 18.3164C8.53522 18.3789 8.97971 18.2651 9.33333 17.9998C9.68695 17.7346 9.92074 17.3398 9.98325 16.9022C10.0458 16.4646 9.93188 16.0201 9.66667 15.6665C8.80119 14.5125 8.33333 13.109 8.33333 11.6665"></path><path stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" style="mix-blend-mode:normal" d="M6.66669 5L6.66669 11.6667"></path></svg>
<span>立即投递</span>
</button>
</div>
<div class="home-feature__visual home-feature__visual--referral">
<div class="referral-grid">
<div class="referral-card" v-for="(ref, i) in referralCards" :key="i" :class="{ 'referral-card--offset': i % 2 === 1 }">
<div class="referral-card__icon" :style="{ background: ref.color }">{{ ref.letter }}</div>
<p class="referral-card__company">{{ ref.company }}</p>
<p class="referral-card__role">{{ ref.role }}</p>
<span class="referral-card__code">内推码: {{ ref.code }}</span>
</div>
</div>
</div>
</div>
</section>
<!-- Feature 5: AI求职助手 -->
<section class="home-feature">
<div class="home-feature__inner">
<div class="home-feature__text">
<h2>24h全天候<br/>AI求职助手</h2>
<p>随时提供求职指导从岗位筛选到面试技巧你的专属职业规划顾问</p>
<button class="home-feature__btn" @click="router.push('/jobs')">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="13" height="13" viewBox="0 0 20 20" fill="none"><path d="M18.3333 14.1667C18.3333 14.6087 18.1577 15.0326 17.8452 15.3452C17.5326 15.6577 17.1087 15.8333 16.6667 15.8333L5.69 15.8333C5.24801 15.8334 4.82416 16.0091 4.51167 16.3217L2.67667 18.1567C2.59393 18.2394 2.48851 18.2957 2.37375 18.3186C2.25898 18.3414 2.14003 18.3297 2.03193 18.2849C1.92382 18.2401 1.83142 18.1643 1.76641 18.067C1.7014 17.9697 1.66669 17.8553 1.66667 17.7383L1.66667 4.16667C1.66667 3.72464 1.84227 3.30072 2.15483 2.98816C2.46739 2.67559 2.89131 2.5 3.33334 2.5L16.6667 2.5C17.1087 2.5 17.5326 2.67559 17.8452 2.98816C18.1577 3.30072 18.3333 3.72464 18.3333 4.16667L18.3333 14.1667Z" stroke="currentColor" stroke-width="2" stroke-linejoin="round" stroke-linecap="round" style="mix-blend-mode:normal"></path></svg>
<span>立即咨询</span>
</button>
</div>
<div class="home-feature__visual home-feature__visual--ai">
<div class="feature-ai-chat">
<div class="ai-bubble ai-bubble--bot">👋 哈喽我是你的 AI 职业导师正在为你分析即将到来的面试流程</div>
<div class="ai-bubble ai-bubble--user">能帮我模拟一下明天的腾讯面试吗</div>
<div class="ai-bubble ai-bubble--bot">
<p class="ai-bubble__title">准备就绪面试官画像已生成</p>
<div class="ai-bubble__tags">
<span>项目深度与工程化能力</span>
<span>计算机网络与操作系统</span>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- 用户评价区 Testimonials Section -->
<section class="home-testimonials">
<!-- 底层背景色块 -->
<div class="home-testimonials__orb home-testimonials__orb--left"></div>
<div class="home-testimonials__orb home-testimonials__orb--right"></div>
<!-- 内容层白色容器 -->
<div class="home-testimonials__container">
<!-- 标题区 -->
<div class="home-testimonials__header">
<h2>万千毕业生的信赖之选</h2>
<p>真实求职反馈见证每一次从迷茫到上岸</p>
</div>
<!-- 评价卡片横向排列 -->
<div class="home-testimonials__cards">
<div class="testimonial-card" v-for="(t, i) in testimonials" :key="i">
<!-- 引号装饰 SVG -->
<p class="testimonial-card__text">{{ t.text }}</p>
<div class="dflex aliite-e">
<div class="testimonial-card__author">
<img class="testimonial-card__avatar" :src="t.avatar" alt="用户头像" />
<div class="testimonial-card__info">
<p class="testimonial-card__name">{{ t.name }}</p>
<p class="testimonial-card__school">{{ t.school }}</p>
</div>
</div>
<img class="w48 h48" src="@/assets/images/home/blue-icon-768.png" alt="">
</div>
</div>
</div>
<!-- 创始人引言卡片可切换 -->
<div class="home-testimonials__founder">
<!-- 装饰圆环 SVG -->
<svg class="home-testimonials__founder-decor" width="455" height="455" viewBox="0 0 455 455" fill="none">
<circle cx="227.5" cy="227.5" r="189.5" stroke="#fff" stroke-width="20" opacity="0.08"/>
<path d="M240 190 L318 224 Q328 227.5 318 231 L240 265 Q227.5 270 215 265 L137 231 Q127 227.5 137 224 L215 190 Q227.5 185 240 190 Z" stroke="#fff" stroke-width="20" fill="none" opacity="0.08" transform="rotate(-45 227.5 227.5)"/>
</svg>
<div class="home-testimonials__founder-content">
<img class="home-testimonials__founder-img" :src="avatarImg" alt="创始人头像" />
<div class="home-testimonials__founder-text">
<blockquote>很多大学生还在用传统方式找工作海投反复改简历效率很低Offer派利用AI技术让专业和岗位<span class="fw600">深度匹配</span>关联配合<span class="fw600">自动化投递</span>让校招求职流程更丝滑更精准节省了80%的繁琐过程拿到更多Offer</blockquote>
<cite>
<span class="cite-name">{{ founderQuotes[founderIndex].name }}</span>
<span class="cite-role">{{ founderQuotes[founderIndex].role }}</span>
</cite>
</div>
</div>
</div>
<!-- 分页小圆点 -->
<div v-if="false" class="home-testimonials__dots">
<span
v-for="(_, i) in founderQuotes"
:key="i"
class="home-testimonials__dot"
:class="{ 'home-testimonials__dot--active': founderIndex === i }"
@click="founderIndex = i"
></span>
</div>
</div>
</section>
<!-- FAQ Section -->
<section class="home-faq">
<div class="home-faq__inner">
<div class="home-faq__header">
<h2>常见问题</h2>
<p>关于平台使用隐私安全与岗位来源</p>
</div>
<div class="home-faq__list">
<!-- 中间Tab导航 -->
<div class="home-nav__tabs">
<div
v-for="(faq, i) in faqs"
:key="i"
class="faq-item"
:class="{ 'faq-item--open': faqOpen === i }"
v-for="tab in navTabs"
:key="tab.key"
class="home-nav__tab"
:class="{ 'home-nav__tab--active': activeTab === tab.key }"
@click="activeTab = tab.key"
>
<div class="faq-item__header" @click="faqOpen === i ? null : (faqOpen = i)">
<span class="faq-item__question">{{ faq.q }}</span>
<div class="faq-item__icon" @click.stop="faqOpen = faqOpen === i ? -1 : i">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none">
<template v-if="faqOpen === i">
<line x1="7" y1="7" x2="17" y2="17" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<line x1="17" y1="7" x2="7" y2="17" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</template>
<template v-else>
<line x1="5" y1="12" x2="19" y2="12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
<line x1="12" y1="5" x2="12" y2="19" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
</template>
</svg>
</div>
</div>
<div class="faq-item__answer" v-show="faqOpen === i">
<!-- 最后一项"还有其他问题"显示文本输入框+提交按钮 -->
<template v-if="i === faqs.length - 1">
<div class="faq-item__feedback">
<textarea
v-model="faqFeedbackText"
class="faq-item__textarea"
placeholder="请输入你的问题"
rows="4"
></textarea>
<button class="faq-item__submit" @click.stop="submitFaqFeedback">提交</button>
</div>
</template>
<template v-else>
<p>{{ faq.a }}</p>
</template>
</div>
<span>{{ tab.label }}</span>
</div>
</div>
<div class="dflex-end aliite-c">
<!-- 未登录时显示登录和立即加入按钮 -->
<div v-if="!store.state.isAuthenticated" class="fs14 color-1 mr40 fw600 cursor-po" @click="handleLoginClick">登录</div>
<button v-if="!store.state.isAuthenticated" class="home-nav__btn" @click="router.push('/jobs')">立即加入</button>
<!-- 已登录时显示进入平台按钮 -->
<button v-else class="home-nav__btn" @click="router.push('/jobs')">进入平台</button>
</div>
</div>
</section>
</header>
<!-- CTA Section -->
<section class="home-cta">
<!-- Tab内容区域 -->
<component :is="activeTabComponent" />
<!-- CTA Section首页tab内容自带岗位雷达自带校招公告自带求职辅导自带其他tab也复用 -->
<section v-if="activeTab !== 'home' && activeTab !== 'jobRadar' && activeTab !== 'announcement' && activeTab !== 'mentor' && activeTab !== 'about'" class="home-cta">
<div class="home-cta__inner">
<h2>让Offer派带你找到<br/>人生第一份好工作</h2>
<button class="home-cta__btn" @click="router.push('/jobs')">免费体验</button>
</div>
</section>
<!-- Job Search Section -->
<section class="home-job-search">
<div class="home-job-search__inner">
<h3>一键找到最适合你的工作</h3>
<div class="home-job-search__filters">
<!-- 行业选择器 -->
<IndustrySelector
:industryIds="homeIndustryIds"
:level="2"
:maxSelect="3"
:allowParentSelect="false"
:allowParentSelectClick="true"
:triggerStyle="filterTriggerStyle"
:displayStyle="filterDisplayStyle"
@update:industryIds="onHomeIndustryChange"
/>
<!-- 地区选择器 -->
<RegionSelector
:regionCodes="homeRegionCodes"
:level="2"
:maxSelect="3"
:triggerStyle="filterTriggerStyle"
:displayStyle="filterDisplayStyle"
@update:regionCodes="onHomeRegionChange"
/>
<!-- 岗位选择器 -->
<JobCategorySelector
:categoryIds="homeCategoryIds"
:level="3"
:maxSelect="3"
:allowParentSelect="false"
:allowParentSelectClick="true"
:triggerStyle="filterTriggerStyle"
:displayStyle="filterDisplayStyle"
@update:categoryIds="onHomeCategoryChange"
/>
<button class="filter-btn" @click="goSearchJobs">立即匹配</button>
</div>
</div>
</section>
<!-- Footer -->
<footer class="home-footer">
<div class="home-footer__inner">
<div class="home-footer__col">
<div class="home-footer__logo">
<!-- 网站Logo图片 -->
<img src="@/assets/images/logo.png" alt="Offer派" class="home-footer__logo-img" />
</div>
<p class="home-footer__slogan">大学生AI求职平台</p>
@@ -466,7 +66,6 @@
<li><a href="https://www.jianshixingqiu.com/internship" target="_blank">名企实习</a></li>
<li><a href="https://www.jianshixingqiu.com/biunique" target="_blank">名企导师1V1</a></li>
<li><a href="https://www.jianshixingqiu.com/directTrain" target="_blank">Offer直通车</a></li>
</ul>
</div>
<div class="home-footer__col">
@@ -492,15 +91,12 @@
import { ref, onMounted, onUnmounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useStore } from 'vuex'
import avatarImg from '@/assets/images/home/avatar-temporary.png'
import studentAvatar0 from '@/assets/images/home/student-avatar-0.png'
import studentAvatar1 from '@/assets/images/home/student-avatar-1.png'
import studentAvatar2 from '@/assets/images/home/student-avatar-2.png'
import IndustrySelector from '@/components/tools/IndustrySelector.vue'
import RegionSelector from '@/components/tools/RegionSelector.vue'
import JobCategorySelector from '@/components/tools/JobCategorySelector.vue'
import AgreementPreviewDialog from '@/components/tools/AgreementPreviewDialog.vue'
import { getDailyJobCount } from '@/utils/dailyJobCount'
import HomeContent from '@/components/HomeContent.vue'
import HomeJobRadar from '@/components/HomeJobRadar.vue'
import HomeAnnouncement from '@/components/HomeAnnouncement.vue'
import HomeMentor from '@/components/HomeMentor.vue'
import HomeAbout from '@/components/HomeAbout.vue'
/** 路由实例 */
const router = useRouter()
@@ -510,133 +106,46 @@ const store = useStore()
// _czc
const _czc: any[] = (window as any)._czc || [];
(window as any)._czc = _czc;
// siteid siteid "XXXXXXXX"
_czc.push(["_setAccount", "1281490571"]);
/** 页面是否已向下滚动超过 20px — 用于导航栏背景切换 */
// ==================== Tab ====================
/** 导航Tab列表 */
const navTabs = [
{ key: 'home', label: '首页' },
{ key: 'jobRadar', label: '岗位雷达' },
{ key: 'announcement', label: '校招公告' },
{ key: 'mentor', label: '求职辅导' },
{ key: 'about', label: '关于我们' },
]
/** 当前激活的Tab */
const activeTab = ref('home')
/** 当前激活Tab对应的组件 */
const activeTabComponent = computed(() => {
switch (activeTab.value) {
case 'home':
return HomeContent
case 'jobRadar':
return HomeJobRadar
case 'announcement':
return HomeAnnouncement
case 'mentor':
return HomeMentor
case 'about':
return HomeAbout
default:
return HomeContent
}
})
// ==================== ====================
/** 页面是否已向下滚动超过 20px */
const isScrolled = ref(false)
/** 今日更新岗位数量 — 每日确定性变化(使用公共哈希工具) */
const dailyJobCount = getDailyJobCount()
/** 当前展开的 FAQ 索引,-1 表示全部收起 */
const faqOpen = ref(-1)
/** 创始人引言当前索引 — 点击小圆点可切换 */
const founderIndex = ref(0)
/** 创始人引言数据 — 支持多条切换 */
const founderQuotes = [
{ text: '“很多大学生还在用传统方式找工作,海投、反复改简历,效率很低。Offer派利用AI技术让专业和岗位深度匹配关联,配合自动化投递,让校招求职流程更丝滑,更精准,节省了80%的繁琐过程,拿到更多Offer。”', name: 'TK', role: 'Offer派创始人' },
]
/** 岗位滚动展示数据 — 模拟最新发布的校招岗位 */
const tickerJobs = [
{ company: '字节跳动', time: '15分钟前', title: '高级前端开发工程师' },
{ company: '腾讯', time: '30分钟前', title: '后端开发工程师' },
{ company: '阿里巴巴', time: '1小时前', title: '产品经理' },
{ company: '华为', time: '2小时前', title: '算法工程师' },
{ company: '京东', time: '3小时前', title: '运营管培生' },
]
/** 内推人脉卡片数据 — letter: 公司首字母, color: 图标背景色 */
const referralCards = [
{ letter: 'b', company: '字节跳动', role: '前端工程师', code: 'r7u9xx', color: '#2563eb' },
{ letter: 'a', company: '阿里巴巴', role: '运营管培生', code: 'alicc2', color: '#ea580c' },
{ letter: 't', company: '腾讯', role: '后端开发工程师', code: 'tx9901', color: '#111827' },
{ letter: 'h', company: '华为', role: '通用软件研究员', code: 'hwl92', color: '#dc2626' },
]
/** 用户评价数据 — 展示真实用户的使用反馈 */
const testimonials = [
{ text: '“Offer派求职体验特别好!岗位信息丰富、质量高,AI岗位匹配也很精准,求职助手给的面试建议很专业,一周就拿到了offer!”', name: '李同学', school: '西安交通大学', avatar: studentAvatar0 },
{ text: '“界面设计简洁友好,用起来很舒适。AI助手帮我理清了方向,让迷茫的我建立了自信,还能帮我优化简历,真的很nice~”', name: '王同学', school: '北京大学', avatar: studentAvatar1 },
{ text: '“满分!特别是网申自动填写,不仅可以一键填写,还能快速定制简历内容,完美匹配岗位要求,毕设求职两不误,真心推荐。”', name: '张同学', school: '中国人民大学', avatar: studentAvatar2 },
]
/** 用户反馈输入内容 */
const faqFeedbackText = ref('')
/** 提交用户反馈 */
function submitFaqFeedback() {
if (!faqFeedbackText.value.trim()) return
// TODO:
faqFeedbackText.value = ''
faqOpen.value = -1
}
/** 常见问题列表 — q: 问题, a: 答案,点击可展开/收起 */
const faqs = [
{ q: '这个平台与其他求职平台有什么不同?', a: '不同于BOSS直聘等传统平台的“手动式”求职,Offer派如同一位经验丰富的职业导师,通过AI能力搭建智能工具,自动处理简历优化、岗位匹配、申请投递等环节,大幅度提升你的求职效率。' },
{ q: '平台如何保障我的个人信息安全?', a: 'Offer派高度重视你的隐私安全。未经你明确同意,我们绝不会将你的个人信息提供给任何第三方。你的所有数据仅用于在平台内为你提供AI智能岗位匹配、简历优化等个性化求职服务。' },
{ q: '平台可以免费使用吗?', a: 'Offer派为所有用户免费开放3天会员权益,帮你省心省力找工作。3天后,依然可以免费使用各项功能,但有使用次数限制。如果你需要不受限制地使用所有功能,更快更高效地获得Offer,建议您升级为付费会员,让求职效率翻倍。' },
{ q: '平台的岗位来源是什么?', a: 'Offer派的岗位来自上万家企业的官方招聘网站,同时聚合了各大主流平台的最新职位——你不需要在多个网站之间来回切换,就能一站式搜罗全网高质量的工作机会。此外,我们会持续筛查每一则新发布的职位,及时剔除虚假信息,帮你避“坑”,为你提供真实靠谱的岗位信息。' },
{ q: '还有其他问题?', a: '' },
]
// ==================== // ====================
/** 首页选中的行业 ID 数组 — 读写 store.jobIntention */
const homeIndustryIds = computed<number[]>(() => store.state.jobIntention.industryIds || [])
/** 首页选中的地区编码数组 — 读写 store.jobIntention */
const homeRegionCodes = computed<string[]>(() => store.state.jobIntention.regionCodes || [])
/** 首页选中的岗位 ID 数组 — 读写 store.jobIntention */
const homeCategoryIds = computed<number[]>(() => store.state.jobIntention.categoryIds || [])
/** 选择器触发按钮样式 — 匹配首页 .filter-select 的外观 */
const filterTriggerStyle = {
width: '2.80rem',
'max-width': 'none',
height: '0.56rem',
'border-radius': '0.16rem',
border: '1px solid #e5e7eb',
padding: '0 0.2rem',
background: '#F3F4F5',
'justify-content': 'space-between',
}
/** 选择器显示文字样式 — 匹配首页 .filter-select 内文字 */
const filterDisplayStyle = {
'font-size': '0.16rem',
'line-height': '0.16rem',
color: 'rgba(0, 0, 0, 0.45)',
'max-width': '1.8rem',
}
/** 行业选择变更回调 — 保存到 store */
function onHomeIndustryChange(ids: number[]) {
store.dispatch('saveJobIntention', {
...store.state.jobIntention,
industryIds: ids,
})
}
/** 地区选择变更回调 — 保存到 store */
function onHomeRegionChange(codes: string[]) {
store.dispatch('saveJobIntention', {
...store.state.jobIntention,
regionCodes: codes,
})
}
/** 岗位选择变更回调 — 保存到 store */
function onHomeCategoryChange(ids: number[]) {
store.dispatch('saveJobIntention', {
...store.state.jobIntention,
categoryIds: ids,
})
}
/** 点击搜索职位 — 跳转到 Jobs 页面 */
function goSearchJobs() {
router.push('/jobs')
}
onMounted(() => {
// 20px
const isScaleMode = () => {
return !!document.body.style.transform && document.body.style.transform.includes('scale')
}
@@ -652,13 +161,11 @@ onMounted(() => {
if (isScaleMode()) {
const el = document.querySelector('.home-nav__inner') as HTMLElement | null
if (el && isScrolled.value) {
// fixed transform 退 absolute top
el.style.top = (document.body.scrollTop + 20) + 'px'
} else if (el && !isScrolled.value) {
el.style.top = ''
}
} else {
// scale top CSS
const el = document.querySelector('.home-nav__inner') as HTMLElement | null
if (el && el.style.top) {
el.style.top = ''
@@ -677,18 +184,18 @@ onMounted(() => {
document.body.removeEventListener('scroll', handleScroll)
})
// URL invite_code
// URL
const urlParams = new URLSearchParams(window.location.search)
const inviteCode = urlParams.get('invite_code')
if (inviteCode && inviteCode.length === 10) {
store.commit('SET_INVITE_CODE', inviteCode)
}
//
//
store.dispatch('loadCommonData')
})
/** 点击登录按钮 — 打开登录弹窗,登录成功后跳转到 jobs 页面 */
/** 点击登录按钮 */
function handleLoginClick() {
store.dispatch('openLogin', '/jobs')
}
+5
View File
@@ -452,6 +452,11 @@ onMounted(() => {
/** 返回职位列表页 */
function goBack() {
//
if (route.query.from === 'home') {
router.push('/jobs')
return
}
if (window.history.length > 1) {
router.back()
} else {
+19 -1
View File
@@ -1624,6 +1624,8 @@ async function loadJobList() {
console.error('加载岗位列表失败', e)
} finally {
loading.value = false
//
nextTick(() => { autoFillIfNeeded() })
}
//
loadFilterJobCount()
@@ -1657,6 +1659,22 @@ async function loadNextPage() {
console.error('加载下一页失败', e)
} finally {
loadingMore.value = false
//
nextTick(() => { autoFillIfNeeded() })
}
}
/** 自动填满容器:登录状态下,当推荐列表内容不足以产生滚动条且还有更多数据时,自动加载下一页 */
function autoFillIfNeeded() {
//
if (!isAuthenticated.value) return
// Tab
if (!isRecommendTab.value) return
const el = jobListRef.value
if (!el) return
//
if (el.scrollHeight <= el.clientHeight && !noMore.value && !loadingMore.value && !loading.value) {
loadNextPage()
}
}
@@ -1712,7 +1730,7 @@ const filterCountLoading = ref(false)
/** 是否显示筛选数量与深度匹配提示条(数量 < 2000 时显示) */
const showFilterCountBar = computed(() => {
return filterJobCount.value !== null && filterJobCount.value < 2000 && isRecommendTab.value
return filterJobCount.value !== null && filterJobCount.value < 5000 && isRecommendTab.value
})
/** 是否正在进行深度匹配查询 */
+5
View File
@@ -218,6 +218,11 @@ async function loadDetail() {
/** 返回导师列表页 */
function goBack() {
//
if (route.query.from === 'home') {
router.push('/mentor')
return
}
if (window.history.length > 1) {
router.back()
} else {
+210 -146
View File
@@ -2,89 +2,111 @@
<div class="resume-page dflex">
<SideNav />
<div class="resume-page__content">
<!-- 页面标题 + 上传按钮 -->
<!-- 页面标题 -->
<div class="resume-page__header">
<h2 class="resume-page__title">我的简历</h2>
<div class="dflex-end">
<!-- 从个人资料创建按钮 -->
<button class="resume-page__create-btn mr16" :disabled="creatingFromProfile" @click="handleCreateFromProfile">
<svg viewBox="0 0 16 16" fill="none" class="resume-page__upload-icon">
<path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
从个人资料创建
</button>
<button class="resume-page__upload-btn" @click="handleUpload">
<svg viewBox="0 0 16 16" fill="none" class="resume-page__upload-icon">
<path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
</svg>
上传简历
</button>
</div>
<h2 class="resume-page__title">简历管理</h2>
</div>
<!-- 简历 -->
<div class="resume-page__table-wrap">
<!-- 空状态提示 -->
<div v-if="resumeList.length === 0" class="resume-page__empty">
<p>暂无简历请点击上方按钮上传简历</p>
</div>
<table v-else class="resume-page__table">
<thead>
<tr>
<th class="resume-page__th">简历</th>
<th class="resume-page__th">目标岗位</th>
<th class="resume-page__th">最近修改</th>
<th class="resume-page__th resume-page__th--action"></th>
</tr>
</thead>
<tbody>
<tr
v-for="item in resumeList"
:key="item.id"
class="resume-page__row"
@click="goDetail(item.id)"
<!-- 简历卡片网 -->
<div ref="gridRef" class="resume-page__card-grid scrollbar-thin" @scroll="onGridScroll">
<!-- 简历卡片列表 -->
<div
v-for="item in resumeList"
:key="item.id"
class="resume-page__card"
@click="goDetail(item.id)"
>
<!-- 预览图区域 -->
<div class="resume-page__card-preview">
<!-- 默认标记 -->
<span v-if="item.isDefault" class="resume-page__card-default-tag">默认</span>
<!-- 更多按钮右上角 -->
<button
class="resume-page__card-more-btn"
@click.stop="toggleMenu(item.id)"
>
<td class="resume-page__td">
<div class="resume-page__name-cell">
<span class="resume-page__name">{{ item.name }}</span>
<span v-if="item.isDefault" class="resume-page__default-tag">默认</span>
</div>
</td>
<td class="resume-page__td resume-page__td--ellipsis">{{ item.targetJob }}</td>
<td class="resume-page__td">{{ item.updatedAt }}</td>
<!-- 操作按钮列 -->
<td class="resume-page__td resume-page__td--action">
<div class="resume-page__actions">
<button
class="resume-page__action-btn"
@click.stop="handleEditName(item)"
>
<svg width="12" height="12" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.0523 1.82016C11.7781 1.54295 11.4366 1.34358 11.062 1.24212C10.6875 1.14065 10.2931 1.14065 9.91851 1.24212C9.54396 1.34358 9.2024 1.54295 8.92822 1.82016L8.29348 2.46298C8.2617 2.4886 8.23269 2.51753 8.20693 2.54932L1.90565 8.92043C1.82508 9.00168 1.77008 9.10517 1.74754 9.21793L1.16589 12.1346C1.14691 12.2288 1.1513 12.3264 1.17863 12.4185C1.20598 12.5107 1.25544 12.5945 1.32261 12.6627C1.38979 12.7308 1.47258 12.7811 1.56364 12.809C1.65458 12.837 1.75127 12.8417 1.84449 12.8229L4.7343 12.2396C4.84585 12.2168 4.94823 12.1612 5.02859 12.0797L12.0523 4.97947C12.3265 4.70229 12.5237 4.35703 12.6241 3.97838C12.7245 3.59974 12.7245 3.20105 12.6241 2.8224C12.5237 2.44375 12.3265 2.09849 12.0523 1.82133" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M4.3353 11.1303L2.46814 11.5083L2.84319 9.62298L8.65699 3.74536L10.1491 5.25385L4.3353 11.1303ZM9.7439 2.64628C9.8742 2.51131 10.0376 2.41368 10.2174 2.36336C10.3972 2.31304 10.587 2.31183 10.7675 2.35985C10.9479 2.40787 11.1125 2.5034 11.2445 2.63669C11.3765 2.76999 11.4711 2.93629 11.5187 3.11863C11.5664 3.30096 11.5653 3.4928 11.5157 3.67459C11.466 3.85638 11.3696 4.0216 11.2362 4.15341L10.9661 4.42871L9.47384 2.91925L9.7439 2.64628Z" fill="white"/>
</svg>
<span class="fs12 color-middle ml3 color-dark-ho">修改简历信息</span>
</button>
<button
v-if="!item.isDefault"
class="resume-page__action-btn"
@click.stop="handleSetDefault(item.id)"
>设为默认</button>
<button
class="resume-page__action-btn"
@click.stop="handleExport(item)"
>导出</button>
<button
class="resume-page__action-btn resume-page__action-btn--delete"
@click.stop="handleDelete(item.id, item.isDefault)"
>删除</button>
</div>
</td>
</tr>
</tbody>
</table>
<svg viewBox="0 0 24 24" fill="none">
<circle cx="6" cy="12" r="2" fill="#52CAD1"/>
<circle cx="12" cy="12" r="2" fill="#52CAD1"/>
<circle cx="18" cy="12" r="2" fill="#52CAD1"/>
</svg>
</button>
<!-- 更多菜单 -->
<div v-if="activeMenuId === item.id" class="resume-page__card-menu" @click.stop>
<!-- 设为默认 -->
<div
v-if="!item.isDefault"
class="resume-page__card-menu-item resume-page__card-menu-item--default"
@click.stop="handleSetDefault(item.id)"
>
<svg viewBox="0 0 16 16" fill="none">
<path d="M8 1.5L9.79 5.12L13.8 5.71L10.9 8.53L11.58 12.52L8 10.63L4.42 12.52L5.1 8.53L2.2 5.71L6.21 5.12L8 1.5Z" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round" fill="none"/>
</svg>
<span>设为默认</span>
</div>
<!-- 导出简历 -->
<div
class="resume-page__card-menu-item"
@click.stop="handleExport(item)"
>
<svg viewBox="0 0 16 16" fill="none">
<path d="M8 2v8M8 2L5 5M8 2l3 3" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 10v3a1 1 0 001 1h10a1 1 0 001-1v-3" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span>导出简历</span>
</div>
<!-- 删除简历 -->
<div
class="resume-page__card-menu-item resume-page__card-menu-item--delete"
@click.stop="handleDelete(item.id, item.isDefault)"
>
<svg viewBox="0 0 16 16" fill="none">
<path d="M3 4h10M5.5 4V3a1 1 0 011-1h3a1 1 0 011 1v1M6.5 7v4M9.5 7v4" stroke="#EF4444" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4 4l.7 8.5a1 1 0 001 .9h4.6a1 1 0 001-.9L12 4" stroke="#EF4444" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
<span style="color: #6A7282;">删除简历</span>
</div>
</div>
<!-- 预览图片 -->
<img src="@/assets/images/resume-list-img01.png" alt="简历预览" />
</div>
<!-- 底部信息区域 -->
<div class="resume-page__card-info">
<!-- 名称 + 编辑按钮 -->
<div class="resume-page__card-info-header">
<span class="resume-page__card-name">{{ item.name }}</span>
<!-- 编辑按钮笔图标 -->
<button class="resume-page__card-edit-btn" @click.stop="handleEditName(item)">
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.16667 17.5H15.8333C16.75 17.5 17.5 16.75 17.5 15.8333V9.16667H15.8333V15.8333H4.16667V4.16667H10.8333V2.5H4.16667C3.25 2.5 2.5 3.25 2.5 4.16667V15.8333C2.5 16.75 3.25 17.5 4.16667 17.5Z" fill="#52CAD1"/>
<path d="M6.83334 10.6662V12.3329C6.83334 12.7912 7.20834 13.1662 7.66668 13.1662H9.33334C9.55834 13.1662 9.76668 13.0746 9.92501 12.9246L15.3 7.54956L12.4583 4.7079L7.08334 10.0829C7.00611 10.1608 6.945 10.2531 6.90353 10.3546C6.86206 10.4562 6.84104 10.5649 6.84168 10.6746L6.83334 10.6662ZM17.2583 5.59123C17.3356 5.51413 17.3969 5.42256 17.4387 5.32175C17.4805 5.22094 17.502 5.11287 17.502 5.00373C17.502 4.89459 17.4805 4.78652 17.4387 4.68571C17.3969 4.5849 17.3356 4.49332 17.2583 4.41623L15.5917 2.74956C15.5146 2.67231 15.423 2.61102 15.3222 2.5692C15.2214 2.52738 15.1133 2.50586 15.0042 2.50586C14.895 2.50586 14.787 2.52738 14.6862 2.5692C14.5853 2.61102 14.4938 2.67231 14.4167 2.74956L13.0417 4.12456L15.8833 6.96623L17.2583 5.59123Z" fill="#52CAD1"/>
</svg>
</button>
</div>
<!-- 目标岗位 -->
<div class="resume-page__card-target">{{ item.targetJob || '' }}</div>
<!-- 最后编辑时间hover时显示 -->
<div class="resume-page__card-edit-time">最后编辑于{{ item.updatedAtDate }}</div>
</div>
</div>
<!-- 新建卡片始终在最后 -->
<div class="resume-page__card-new" @click="handleUpload">
<svg viewBox="0 0 32 32" fill="none">
<circle cx="16" cy="16" r="16" fill="#52CAD1"/>
<path d="M16 10v12M10 16h12" stroke="#fff" stroke-width="2.5" stroke-linecap="round"/>
</svg>
<span>新建</span>
</div>
<!-- 加载中 -->
<div v-if="loadingMore" class="resume-page__loading-box">
<span>加载中...</span>
</div>
</div>
</div>
<!-- 编辑简历名称弹窗 -->
<ResumeEditNameDialog
v-model="editNameVisible"
@@ -106,8 +128,10 @@
v-model="uploadDialogVisible"
@confirm="handleUploadConfirm"
/>
<!-- 全屏加载遮罩 -->
<FullscreenLoading :visible="fullLoading" :text="fullLoadingText" title="Nova 正在帮您努力处理~" />
<!-- 简历解析步骤进度遮罩 -->
<StepProgressOverlay
v-if="showUploadProgress"
@@ -123,19 +147,17 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { EditPen } from '@element-plus/icons-vue'
import SideNav from '@/components/SideNav.vue'
import ResumeEditNameDialog from '@/components/ResumeEditNameDialog.vue'
import ResumeExportDialog from '@/components/ResumeExportDialog.vue'
import ResumeUploadDialog from '@/components/ResumeUploadDialog.vue'
import { uploadResume } from '@/utils/aiRequest'
import {
fetchResumeList, deleteResume, setDefaultResume, createResumeFromProfile, type ResumeListItem,
fetchResumeList, deleteResume, setDefaultResume, type ResumeListItem,
} from '@/api/resume'
import { ElMessage, ElMessageBox } from 'element-plus'
//
import 'element-plus/es/components/message-box/style/css'
import FullscreenLoading from '@/components/FullscreenLoading.vue'
import StepProgressOverlay from '@/components/StepProgressOverlay.vue'
@@ -145,7 +167,6 @@ const router = useRouter()
// _czc
const _czc: any[] = (window as any)._czc || [];
(window as any)._czc = _czc;
// siteid siteid "XXXXXXXX"
_czc.push(["_setAccount", "1281490571"]);
// ==================== ====================
@@ -154,62 +175,44 @@ const fullLoading = ref(false)
/** 全屏加载文案 */
const fullLoadingText = ref('')
// ==================== ====================
/** 头像背景色列表,按索引循环取色 */
const avatarColors = ['#1A1A2E', '#4FC2C9', '#F2994A', '#6C5CE7', '#E17055', '#00B894', '#FDCB6E']
// ==================== ====================
/** 简历列表项(前端展示用) */
interface ResumeItem {
id: string
name: string
avatarLetter: string
avatarColor: string
isDefault: boolean
targetJob: string
updatedAt: string
createdAt: string
updatedAtDate: string
}
// ==================== ====================
/**
* 将毫秒时间戳转为友好的相对时间文案
* @param timestamp 后端返回的毫秒级时间戳
* 将毫秒时间戳转为日期格式 YYYY-MM-DD
*/
function formatTime(timestamp?: number): string {
if (!timestamp) return '-'
function formatDate(timestamp?: number): string {
if (!timestamp) return ''
const date = new Date(timestamp)
const now = Date.now()
const diff = now - date.getTime()
const minutes = Math.floor(diff / 60000)
if (minutes < 1) return '刚刚'
if (minutes < 60) return `${minutes}分钟前`
const hours = Math.floor(minutes / 60)
if (hours < 24) return `${hours}小时前`
const days = Math.floor(hours / 24)
if (days < 30) return `${days}天前`
const months = Math.floor(days / 30)
if (months < 12) return `${months}个月前`
return `${Math.floor(months / 12)}年前`
const y = date.getFullYear()
const m = String(date.getMonth() + 1).padStart(2, '0')
const d = String(date.getDate()).padStart(2, '0')
return `${y}-${m}-${d}`
}
/**
* 将后端简历数据转为前端展示结构
*/
function mapResumeItem(item: ResumeListItem, index: number): ResumeItem {
function mapResumeItem(item: ResumeListItem): ResumeItem {
const name = item.resumeName || '未命名简历'
return {
id: String(item.id ?? ''),
name,
avatarLetter: name.charAt(0) || '?',
avatarColor: avatarColors[index % avatarColors.length],
isDefault: item.isDefault === 1,
targetJob: item.targetPosition || '',
updatedAt: formatTime(item.updateTime),
createdAt: formatTime(item.createTime),
updatedAt: String(item.updateTime || ''),
updatedAtDate: formatDate(item.updateTime),
}
}
@@ -217,50 +220,101 @@ function mapResumeItem(item: ResumeListItem, index: number): ResumeItem {
/** 简历列表 */
const resumeList = ref<ResumeItem[]>([])
/** 当前页码 */
const pageNum = ref(1)
/** 每页条数 */
const pageSize = ref(20)
/** 是否加载更多中 */
const loadingMore = ref(false)
/** 是否无更多数据 */
const hasNoMoreData = ref(false)
/** 首次加载中 */
const loading = ref(false)
/** 网格容器ref */
const gridRef = ref<HTMLElement | null>(null)
/** 加载简历列表 */
/** 加载简历列表(首页) */
async function loadResumeList() {
loading.value = true
pageNum.value = 1
hasNoMoreData.value = false
try {
const res = await fetchResumeList()
const res = await fetchResumeList(pageSize.value)
if (res.code === '0' && res.data) {
resumeList.value = res.data.map(mapResumeItem)
// pageSize
if (res.data.length < pageSize.value) {
hasNoMoreData.value = true
}
}
} catch (e) {
console.error('获取简历列表失败', e)
} finally {
loading.value = false
//
nextTick(() => { autoFillIfNeeded() })
}
}
// ==================== ====================
onMounted(() => {
loadResumeList()
})
// ==================== ====================
/** 防抖锁:防止多次点击 */
const creatingFromProfile = ref(false)
/**
* 从个人资料创建简历带防抖
*/
async function handleCreateFromProfile() {
if (creatingFromProfile.value) return
creatingFromProfile.value = true
/** 加载下一页 */
async function loadNextPage() {
if (loadingMore.value || hasNoMoreData.value) return
loadingMore.value = true
pageNum.value++
try {
const res = await createResumeFromProfile()
if (res.code === '0') {
ElMessage.success('创建成功')
await loadResumeList()
const res = await fetchResumeList(pageSize.value)
if (res.code === '0' && res.data) {
if (res.data.length === 0) {
hasNoMoreData.value = true
pageNum.value--
} else {
resumeList.value.push(...res.data.map(mapResumeItem))
if (res.data.length < pageSize.value) {
hasNoMoreData.value = true
}
}
}
} catch (e) {
console.error('从个人资料创建简历失败', e)
pageNum.value--
console.error('加载下一页失败', e)
} finally {
creatingFromProfile.value = false
loadingMore.value = false
nextTick(() => { autoFillIfNeeded() })
}
}
// ==================== ====================
/** 自动填满容器:当列表内容不足以产生滚动条且还有更多数据时,自动加载下一页 */
function autoFillIfNeeded() {
const el = gridRef.value
if (!el) return
if (el.scrollHeight <= el.clientHeight && !hasNoMoreData.value && !loadingMore.value && !loading.value) {
loadNextPage()
}
}
/** 网格滚动事件 — 触底自动加载 */
function onGridScroll() {
const el = gridRef.value
if (!el) return
if (el.scrollHeight - el.scrollTop - el.clientHeight < 100) {
loadNextPage()
}
}
// ==================== ====================
/** 当前展开菜单的简历ID */
const activeMenuId = ref<string>('')
/** 切换菜单 */
function toggleMenu(id: string) {
activeMenuId.value = activeMenuId.value === id ? '' : id
}
/** 点击页面其他区域关闭菜单 */
function closeMenuOnClickOutside() {
activeMenuId.value = ''
}
// ==================== ====================
@@ -279,7 +333,7 @@ const editTargetPosition = ref('')
const exportDialogVisible = ref(false)
/** 当前导出的简历 ID */
const exportResumeId = ref('')
/** 当前导出的简历名称(用于文件名) */
/** 当前导出的简历名称 */
const exportResumeName = ref('')
// ==================== ====================
@@ -299,6 +353,7 @@ function handleEditName(item: ResumeItem) {
/** 设为默认简历 */
async function handleSetDefault(id: string) {
activeMenuId.value = ''
try {
const res = await setDefaultResume(id)
if (res.code === '0') {
@@ -314,6 +369,7 @@ async function handleSetDefault(id: string) {
/** 导出简历 */
function handleExport(item: ResumeItem) {
activeMenuId.value = ''
exportResumeId.value = item.id
exportResumeName.value = item.name || '简历'
exportDialogVisible.value = true
@@ -321,6 +377,7 @@ function handleExport(item: ResumeItem) {
/** 删除简历 */
async function handleDelete(id: string, isDefault: boolean) {
activeMenuId.value = ''
try {
await ElMessageBox.confirm('确定要删除这份简历吗?', '提示', {
confirmButtonText: '确定',
@@ -328,7 +385,7 @@ async function handleDelete(id: string, isDefault: boolean) {
type: 'warning',
})
if(resumeList.value.length<=1||isDefault){
if (resumeList.value.length <= 1 || isDefault) {
ElMessage.warning('默认简历不可删除')
return
}
@@ -352,9 +409,8 @@ function handleUpload() {
uploadDialogVisible.value = true
}
/** 上传弹窗确认回调 — 接收 File 对象,调用接口上传 */
/** 上传弹窗确认回调 */
async function handleUploadConfirm(file: File) {
//
showUploadProgress.value = true
uploadProgressDone.value = false
pendingResumeId.value = ''
@@ -362,11 +418,8 @@ async function handleUploadConfirm(file: File) {
try {
const res = await uploadResume(file)
if (res.code === 0) {
//
loadResumeList()
// ID
pendingResumeId.value = res.data.resumeId
//
uploadProgressDone.value = true
} else {
showUploadProgress.value = false
@@ -390,9 +443,9 @@ const uploadProgressTitle = '正在解析简历'
const uploadProgressSubtitle = '正在识别教育背景、工作经历与技能关键词'
/** 4个步骤名称 */
const uploadProgressSteps = ['教育背景解析...', '工作经历解析...', '技能特长解析...', '个人资料最终确认']
/** 每步时长4600毫秒 */
/** 每步时长 */
const uploadProgressDurations = [4200, 4200, 4200, 4200]
/** 缓存的简历ID(等进度走完后跳转) */
/** 缓存的简历ID */
const pendingResumeId = ref('')
/** 解析进度走完后跳转详情页 */
@@ -413,4 +466,15 @@ function handleUploadProgressClose() {
function goDetail(id: string) {
router.push(`/resume/${id}`)
}
// ==================== ====================
onMounted(() => {
loadResumeList()
document.addEventListener('click', closeMenuOnClickOutside)
})
onBeforeUnmount(() => {
document.removeEventListener('click', closeMenuOnClickOutside)
})
</script>