重构页面模块,新增求职辅助工具、招聘信息浏览与交互、个人信息管理和智能岗位推荐功能,优化用户体验和界面布局。

This commit is contained in:
2026-02-06 20:36:33 +08:00
parent 99c5c5089e
commit 232cb913c2
4 changed files with 1599 additions and 24 deletions

View File

@@ -1,24 +1,428 @@
<template>
<div class="page">
<h2>数据分析页</h2>
<p>后续可以在这里放图表统计数据等可视化内容</p>
<h2>智能岗位推荐模块</h2>
<p class="intro">
基于毕业生的专业背景技能画像与求职偏好实时生成匹配度更高的岗位推荐列表
</p>
<section class="card">
<div class="card-header">
<h3>1. 精准推荐列表</h3>
<div class="filters">
<label>
过滤专业
<select v-model="filter.major">
<option value="">全部</option>
<option value="计算机">计算机相关</option>
<option value="机械">机械相关</option>
<option value="电子">电子信息</option>
</select>
</label>
<label>
最低匹配度
<input
v-model.number="filter.minScore"
type="number"
min="0"
max="100"
/>
</label>
</div>
</div>
<div class="job-list">
<div
v-for="job in filteredJobs"
:key="job.id"
class="job-item"
:class="{ active: selectedJob && selectedJob.id === job.id }"
@click="selectJob(job)"
>
<div class="job-main">
<div class="job-title-row">
<span class="job-title">{{ job.title }}</span>
<span class="job-company">{{ job.company }}</span>
</div>
<div class="job-tags">
<span class="tag">{{ job.majorTag }}</span>
<span class="tag" v-for="skill in job.skills" :key="skill">
{{ skill }}
</span>
</div>
</div>
<div class="job-side">
<div class="match-score">
匹配度
<span class="score-value">{{ job.matchScore }}%</span>
</div>
<button
type="button"
class="small-btn"
@click.stop="toggleCompare(job)"
>
{{ isInCompare(job.id) ? '取消对比' : '加入对比' }}
</button>
</div>
</div>
<p v-if="!filteredJobs.length" class="empty-text">
当前条件下没有推荐岗位可以调低匹配度或清空筛选
</p>
</div>
</section>
<section class="two-columns">
<!-- 推荐理由解析 -->
<section class="card">
<h3>2. 推荐理由解析</h3>
<div v-if="selectedJob" class="reason-panel">
<h4>{{ selectedJob.title }} · {{ selectedJob.company }}</h4>
<ul>
<li>专业契合度{{ selectedJob.reasons.major }}</li>
<li>技能匹配度{{ selectedJob.reasons.skills }}</li>
<li>求职偏好匹配{{ selectedJob.reasons.preference }}</li>
</ul>
<p class="tip">
建议可根据推荐理由优化个人信息填写例如补充缺失技能或调整求职偏好
</p>
</div>
<p v-else class="empty-text">点击左侧推荐列表中的岗位查看推荐理由</p>
</section>
<!-- 岗位对比功能 -->
<section class="card">
<h3>3. 岗位对比功能</h3>
<div v-if="compareList.length" class="compare-table-wrapper">
<table class="compare-table">
<thead>
<tr>
<th>岗位</th>
<th>公司</th>
<th>匹配度</th>
<th>城市</th>
<th>薪资</th>
</tr>
</thead>
<tbody>
<tr v-for="job in compareList" :key="job.id">
<td>{{ job.title }}</td>
<td>{{ job.company }}</td>
<td>{{ job.matchScore }}%</td>
<td>{{ job.city }}</td>
<td>{{ job.salary }}</td>
</tr>
</tbody>
</table>
<p class="tip">
你可以通过匹配度城市和薪资综合对比选择更适合当前阶段发展的岗位
</p>
</div>
<p v-else class="empty-text">
在上方推荐列表中点击加入对比即可在此处查看横向对比结果
</p>
</section>
</section>
</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
type Reason = {
major: string
skills: string
preference: string
}
type Job = {
id: number
title: string
company: string
majorTag: string
skills: string[]
matchScore: number
city: string
salary: string
reasons: Reason
}
const jobs = ref<Job[]>([
{
id: 1,
title: '后端开发工程师',
company: '某互联网科技公司',
majorTag: '计算机',
skills: ['Java', 'SpringBoot', 'MySQL'],
matchScore: 92,
city: '北京',
salary: '18-25K',
reasons: {
major: '与你的“计算机科学与技术”专业高度匹配',
skills: 'Java / 数据库 等技能与岗位需求高度重合',
preference: '工作城市、行业方向均与求职偏好一致'
}
},
{
id: 2,
title: '测试开发工程师',
company: '智能硬件企业',
majorTag: '电子',
skills: ['Python', '自动化测试', '嵌入式'],
matchScore: 85,
city: '深圳',
salary: '15-20K',
reasons: {
major: '与“电子信息工程”专业方向契合',
skills: '具备脚本开发与自动化测试经验',
preference: '符合你设置的南方沿海城市与制造业方向'
}
},
{
id: 3,
title: '机械设计工程师',
company: '装备制造龙头企业',
majorTag: '机械',
skills: ['SolidWorks', '机械设计', '有限元分析'],
matchScore: 78,
city: '上海',
salary: '12-18K',
reasons: {
major: '你的机械相关课程背景与岗位需求较为匹配',
skills: '具备 3D 建模与结构分析能力',
preference: '行业方向匹配“高端制造业”'
}
}
])
const selectedJob = ref<Job | null>(null)
const compareIds = ref<number[]>([])
const filter = reactive({
major: '',
minScore: 70
})
const filteredJobs = computed(() =>
jobs.value.filter((job) => {
const passMajor = filter.major ? job.majorTag.includes(filter.major) : true
const passScore = job.matchScore >= (filter.minScore || 0)
return passMajor && passScore
})
)
const compareList = computed(() =>
jobs.value.filter((job) => compareIds.value.includes(job.id))
)
function selectJob(job: Job) {
selectedJob.value = job
}
function isInCompare(id: number) {
return compareIds.value.includes(id)
}
function toggleCompare(job: Job) {
if (isInCompare(job.id)) {
compareIds.value = compareIds.value.filter((v) => v !== job.id)
} else if (compareIds.value.length < 3) {
compareIds.value.push(job.id)
} else {
alert('最多同时对比 3 个岗位(示例限制)')
}
}
</script>
<style scoped>
.page h2 {
margin-top: 0;
margin-bottom: 8px;
font-size: 18px;
font-size: 20px;
font-weight: 600;
}
.page p {
margin: 0;
.intro {
margin: 0 0 16px;
color: #4b5563;
font-size: 14px;
}
.card {
background: #f9fafb;
border-radius: 12px;
padding: 14px 16px;
border: 1px solid #e5e7eb;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
margin-bottom: 10px;
}
.filters {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-size: 13px;
}
.filters label {
display: inline-flex;
align-items: center;
gap: 4px;
}
select,
input[type='number'] {
border-radius: 8px;
border: 1px solid #d1d5db;
padding: 4px 6px;
font-size: 13px;
}
.job-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.job-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
border-radius: 10px;
background: #ffffff;
border: 1px solid #e5e7eb;
cursor: pointer;
}
.job-item.active {
border-color: #2563eb;
box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.3);
}
.job-main {
flex: 1;
}
.job-title-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: baseline;
}
.job-title {
font-size: 15px;
font-weight: 500;
}
.job-company {
font-size: 13px;
color: #6b7280;
}
.job-tags {
margin-top: 4px;
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.tag {
font-size: 11px;
padding: 2px 6px;
border-radius: 999px;
background: #e0f2fe;
color: #0369a1;
}
.job-side {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 6px;
margin-left: 12px;
}
.match-score {
font-size: 12px;
color: #6b7280;
}
.score-value {
margin-left: 4px;
font-size: 14px;
font-weight: 600;
color: #16a34a;
}
.small-btn {
border-radius: 999px;
border: none;
background: #2563eb;
color: #ffffff;
padding: 4px 10px;
font-size: 12px;
cursor: pointer;
}
.small-btn:hover {
background: #1d4ed8;
}
.two-columns {
margin-top: 16px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 16px;
}
.reason-panel h4 {
margin: 0 0 8px;
font-size: 15px;
}
.reason-panel ul {
padding-left: 18px;
margin: 0 0 8px;
font-size: 13px;
color: #4b5563;
}
.empty-text {
margin-top: 6px;
font-size: 13px;
color: #9ca3af;
}
.tip {
font-size: 12px;
color: #6b7280;
}
.compare-table-wrapper {
overflow-x: auto;
}
.compare-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.compare-table th,
.compare-table td {
padding: 6px 8px;
border: 1px solid #e5e7eb;
text-align: left;
}
.compare-table th {
background: #eff6ff;
}
</style>

View File

@@ -1,24 +1,422 @@
<template>
<div class="page">
<h2>欢迎来到首页</h2>
<p>这里可以展示项目的总体介绍关键指标和快捷入口</p>
<h2>个人信息管理模块</h2>
<p class="intro">
面向理工科毕业生的核心个人画像入口为后续岗位推荐和匹配提供基础数据支持
</p>
<div class="grid">
<!-- 基础信息录入 -->
<section class="card">
<h3>1. 基础信息录入</h3>
<form class="form" @submit.prevent="saveBasicInfo">
<div class="form-row">
<label>专业</label>
<input v-model="basic.major" placeholder="例如:计算机科学与技术" />
</div>
<div class="form-row">
<label>学历</label>
<select v-model="basic.education">
<option value="">请选择</option>
<option value="本科">本科</option>
<option value="硕士">硕士</option>
<option value="博士">博士</option>
<option value="专科">专科</option>
</select>
</div>
<div class="form-row">
<label>毕业院校</label>
<input v-model="basic.university" placeholder="请输入学校名称" />
</div>
<div class="form-row">
<label>技能证书</label>
<input
v-model="basic.certificates"
placeholder="如CET-6、计算机二级、软考等"
/>
</div>
<div class="form-row">
<label>项目经历</label>
<textarea
v-model="basic.projects"
rows="3"
placeholder="简单描述你做过的项目或科研经历"
/>
</div>
<div class="form-actions">
<button type="submit">保存基础信息</button>
<span class="hint">仅本地演示后续可接入后端存储</span>
</div>
</form>
</section>
<!-- 求职偏好设置 -->
<section class="card">
<h3>2. 求职偏好设置</h3>
<form class="form" @submit.prevent="saveJobPreference">
<div class="form-row">
<label>期望岗位类型</label>
<input
v-model="preference.positionType"
placeholder="如:后端开发工程师、测试工程师"
/>
</div>
<div class="form-row">
<label>期望工作地点</label>
<input
v-model="preference.location"
placeholder="如:北京 / 上海 / 杭州,可填写多个"
/>
</div>
<div class="form-row salary-row">
<label>期望薪资范围K/</label>
<div class="salary-inputs">
<input
v-model.number="preference.salaryMin"
type="number"
min="0"
placeholder="最低"
/>
<span class="separator">-</span>
<input
v-model.number="preference.salaryMax"
type="number"
min="0"
placeholder="最高"
/>
</div>
</div>
<div class="form-row">
<label>行业方向</label>
<input
v-model="preference.industry"
placeholder="如:互联网、新能源、制造业等"
/>
</div>
<div class="form-actions">
<button type="submit">保存求职偏好</button>
</div>
</form>
</section>
<!-- 简历管理 -->
<section class="card resume-card">
<h3>3. 简历管理</h3>
<div class="upload-area">
<label class="upload-label">
上传简历文件PDF / Word
<input type="file" class="file-input" @change="handleUpload" />
</label>
<p class="hint">当前仅做界面演示文件不会真正上传</p>
</div>
<div class="resume-list" v-if="resumes.length">
<div class="resume-item" v-for="item in resumes" :key="item.id">
<div>
<div class="resume-title">
{{ item.name }}
<span v-if="item.isDefault" class="tag-default">默认</span>
</div>
<div class="resume-desc">{{ item.description }}</div>
</div>
<div class="resume-actions">
<button type="button" @click="setDefault(item.id)">
设为默认
</button>
<button type="button" @click="editResume(item.id)">在线编辑</button>
<button type="button" class="primary" @click="quickApply(item.id)">
一键投递
</button>
</div>
</div>
</div>
<p v-else class="empty-text">暂时还没有简历请先上传或创建一份</p>
</section>
</div>
</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import { reactive, ref } from 'vue'
const basic = reactive({
major: '',
education: '',
university: '',
certificates: '',
projects: ''
})
const preference = reactive({
positionType: '',
location: '',
salaryMin: undefined as number | undefined,
salaryMax: undefined as number | undefined,
industry: ''
})
type ResumeItem = {
id: number
name: string
description: string
isDefault: boolean
}
const resumes = ref<ResumeItem[]>([
{
id: 1,
name: '通用技术岗简历-v1',
description: '适用于后端 / 测试 / 运维等通用技术岗位',
isDefault: true
},
{
id: 2,
name: '算法岗位简历-v1',
description: '突出数学与算法竞赛经历,适合算法与数据岗位',
isDefault: false
}
])
function saveBasicInfo() {
console.log('保存基础信息', { ...basic })
alert('基础信息已保存(示例)')
}
function saveJobPreference() {
console.log('保存求职偏好', { ...preference })
alert('求职偏好已保存(示例)')
}
function handleUpload(event: Event) {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
if (!file) return
const id = Date.now()
resumes.value.push({
id,
name: file.name,
description: '从本地上传的简历文件,仅作示例展示。',
isDefault: false
})
target.value = ''
}
function setDefault(id: number) {
resumes.value = resumes.value.map((item) => ({
...item,
isDefault: item.id === id
}))
}
function editResume(id: number) {
const item = resumes.value.find((r) => r.id === id)
if (item) {
alert(`打开在线编辑器(示例):${item.name}`)
}
}
function quickApply(id: number) {
const item = resumes.value.find((r) => r.id === id)
if (item) {
alert(`使用【${item.name}】进行一键投递(示例)`)
}
}
</script>
<style scoped>
.page h2 {
margin-top: 0;
margin-bottom: 8px;
font-size: 18px;
font-size: 20px;
font-weight: 600;
}
.page p {
margin: 0;
.intro {
margin: 0 0 16px;
color: #4b5563;
font-size: 14px;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 16px;
}
.card {
background: #f9fafb;
border-radius: 12px;
padding: 14px 16px;
border: 1px solid #e5e7eb;
}
.card h3 {
margin: 0 0 12px;
font-size: 16px;
}
.form {
display: flex;
flex-direction: column;
gap: 10px;
}
.form-row {
display: flex;
flex-direction: column;
gap: 4px;
}
.form-row label {
font-size: 13px;
color: #4b5563;
}
input,
select,
textarea {
border-radius: 8px;
border: 1px solid #d1d5db;
padding: 6px 8px;
font-size: 13px;
outline: none;
}
input:focus,
select:focus,
textarea:focus {
border-color: #2563eb;
box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.3);
}
.salary-row label {
margin-bottom: 4px;
}
.salary-inputs {
display: flex;
align-items: center;
gap: 6px;
}
.salary-inputs input {
width: 80px;
}
.separator {
color: #6b7280;
}
.form-actions {
margin-top: 4px;
display: flex;
align-items: center;
gap: 8px;
}
button {
border-radius: 999px;
border: none;
background: #2563eb;
color: #ffffff;
padding: 6px 14px;
font-size: 13px;
cursor: pointer;
}
button:hover {
background: #1d4ed8;
}
.hint {
font-size: 12px;
color: #9ca3af;
}
.resume-card {
grid-column: 1 / -1;
}
.upload-area {
margin-bottom: 10px;
}
.upload-label {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
border-radius: 999px;
border: 1px dashed #9ca3af;
font-size: 13px;
color: #4b5563;
cursor: pointer;
}
.file-input {
display: none;
}
.resume-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.resume-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
border-radius: 10px;
background: #ffffff;
border: 1px solid #e5e7eb;
}
.resume-title {
font-size: 14px;
font-weight: 500;
display: flex;
align-items: center;
gap: 6px;
}
.tag-default {
padding: 2px 6px;
border-radius: 999px;
background: #0ea5e9;
color: #f9fafb;
font-size: 11px;
}
.resume-desc {
font-size: 12px;
color: #6b7280;
margin-top: 2px;
}
.resume-actions {
display: flex;
gap: 6px;
}
.resume-actions button {
padding-inline: 10px;
}
.resume-actions .primary {
background: #f97316;
}
.resume-actions .primary:hover {
background: #ea580c;
}
.empty-text {
font-size: 13px;
color: #9ca3af;
}
</style>

View File

@@ -1,24 +1,423 @@
<template>
<div class="page">
<h2>报告中心</h2>
<p>在这里管理查看和导出各类报告</p>
<h2>招聘信息浏览与交互模块</h2>
<p class="intro">
为理工科毕业生提供多维度的岗位检索能力以及与企业进行高效互动的入口
</p>
<!-- 多维度搜索 -->
<section class="card">
<h3>1. 多维度搜索</h3>
<form class="filter-bar" @submit.prevent>
<input v-model="filter.keyword" placeholder="输入岗位关键词例如Java、测试" />
<select v-model="filter.major">
<option value="">专业方向</option>
<option value="计算机">计算机相关</option>
<option value="机械">机械相关</option>
<option value="电子">电子信息</option>
</select>
<select v-model="filter.companyType">
<option value="">企业类型</option>
<option value="国企">国企</option>
<option value="外企">外企</option>
<option value="民企">民企</option>
</select>
<select v-model="filter.hot">
<option value="">岗位热度</option>
<option value="高"></option>
<option value="中"></option>
<option value="低"></option>
</select>
</form>
</section>
<section class="layout">
<!-- 岗位列表 -->
<section class="card job-list-card">
<h3>2. 岗位列表</h3>
<div class="job-list">
<div
v-for="job in filteredJobs"
:key="job.id"
class="job-item"
:class="{ active: selectedJob && selectedJob.id === job.id }"
@click="selectJob(job)"
>
<div>
<div class="job-title-row">
<span class="job-title">{{ job.title }}</span>
<span class="job-company">{{ job.company }}</span>
</div>
<div class="job-meta">
<span>{{ job.city }}</span>
<span>{{ job.salary }}</span>
<span>{{ job.companyType }}</span>
</div>
</div>
<div class="job-actions">
<button
type="button"
@click.stop="toggleFavorite(job.id)"
:class="{ secondary: isFavorite(job.id) }"
>
{{ isFavorite(job.id) ? '已收藏' : '收藏' }}
</button>
<button
type="button"
class="primary"
@click.stop="apply(job.id)"
>
{{ isApplied(job.id) ? '已投递' : '投递简历' }}
</button>
</div>
</div>
<p v-if="!filteredJobs.length" class="empty-text">
暂无符合当前筛选条件的岗位可以尝试调整搜索条件
</p>
</div>
</section>
<!-- 岗位详情页 -->
<section class="card detail-card">
<h3>3. 岗位详情</h3>
<div v-if="selectedJob" class="detail">
<h4>{{ selectedJob.title }} · {{ selectedJob.company }}</h4>
<p class="detail-sub">
{{ selectedJob.city }} · {{ selectedJob.salary }} ·
{{ selectedJob.companyType }}
</p>
<h5>岗位职责</h5>
<ul>
<li v-for="(duty, index) in selectedJob.duties" :key="index">
{{ duty }}
</li>
</ul>
<h5>任职要求</h5>
<ul>
<li v-for="(req, index) in selectedJob.requirements" :key="index">
{{ req }}
</li>
</ul>
<h5>企业介绍与薪资福利</h5>
<p class="detail-desc">{{ selectedJob.companyIntro }}</p>
</div>
<p v-else class="empty-text">
在左侧列表中选择一个岗位查看详细信息与岗位要求
</p>
</section>
</section>
</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import { computed, reactive, ref } from 'vue'
type JobItem = {
id: number
title: string
company: string
city: string
salary: string
companyType: string
majorTag: string
hot: '高' | '中' | '低'
duties: string[]
requirements: string[]
companyIntro: string
}
const jobs = ref<JobItem[]>([
{
id: 1,
title: 'Java 后端开发工程师',
company: '云计算科技有限公司',
city: '北京',
salary: '18-25K',
companyType: '民企',
majorTag: '计算机',
hot: '高',
duties: [
'参与后端服务的设计与开发,保证系统稳定性与性能。',
'参与需求评审,与前端及产品协作完成业务功能。',
'编写相关技术文档与单元测试。'
],
requirements: [
'计算机或相关理工科专业,本科及以上学历。',
'熟悉 Java 语言及 SpringBoot 框架。',
'掌握 MySQL、Redis 等常用中间件,具备良好的编码习惯。'
],
companyIntro: '专注云计算与大数据解决方案,为各行业提供技术服务。'
},
{
id: 2,
title: '自动化测试工程师',
company: '智能硬件科技集团',
city: '深圳',
salary: '15-20K',
companyType: '民企',
majorTag: '电子',
hot: '中',
duties: [
'参与产品测试方案设计及测试用例编写。',
'搭建和维护自动化测试框架。',
'跟踪和推动缺陷修复,保障产品质量。'
],
requirements: [
'电子信息、计算机等相关理工科专业。',
'熟悉 Python、Shell 等脚本语言。',
'了解常见自动化测试工具和方法。'
],
companyIntro: '国内领先的智能硬件设计与生产企业,产品覆盖多领域。'
},
{
id: 3,
title: '机械设计工程师',
company: '高端装备制造股份有限公司',
city: '上海',
salary: '12-18K',
companyType: '国企',
majorTag: '机械',
hot: '高',
duties: [
'负责机械部件及整机结构的方案设计与优化。',
'输出二维工程图与三维模型,并跟进生产制造。',
'参与样机装配与测试验证。'
],
requirements: [
'机械设计相关专业,本科及以上学历。',
'熟练使用 SolidWorks、CATIA 等三维设计软件。',
'具备良好的沟通协调能力和团队协作精神。'
],
companyIntro: '国有大型装备制造企业,深耕高端装备领域多年。'
}
])
const filter = reactive({
keyword: '',
major: '',
companyType: '',
hot: ''
})
const selectedJob = ref<JobItem | null>(null)
const favoriteIds = ref<number[]>([])
const appliedIds = ref<number[]>([])
const filteredJobs = computed(() =>
jobs.value.filter((job) => {
const byKeyword = filter.keyword
? job.title.includes(filter.keyword) ||
job.company.includes(filter.keyword)
: true
const byMajor = filter.major ? job.majorTag === filter.major : true
const byCompanyType = filter.companyType
? job.companyType === filter.companyType
: true
const byHot = filter.hot ? job.hot === filter.hot : true
return byKeyword && byMajor && byCompanyType && byHot
})
)
function selectJob(job: JobItem) {
selectedJob.value = job
}
function isFavorite(id: number) {
return favoriteIds.value.includes(id)
}
function toggleFavorite(id: number) {
if (isFavorite(id)) {
favoriteIds.value = favoriteIds.value.filter((v) => v !== id)
} else {
favoriteIds.value.push(id)
}
}
function isApplied(id: number) {
return appliedIds.value.includes(id)
}
function apply(id: number) {
if (!isApplied(id)) {
appliedIds.value.push(id)
alert('简历已投递(示例),可在个人中心查看投递状态。')
}
}
</script>
<style scoped>
.page h2 {
margin-top: 0;
margin-bottom: 8px;
font-size: 18px;
font-size: 20px;
font-weight: 600;
}
.page p {
margin: 0;
.intro {
margin: 0 0 16px;
color: #4b5563;
font-size: 14px;
}
.card {
background: #f9fafb;
border-radius: 12px;
padding: 14px 16px;
border: 1px solid #e5e7eb;
}
.filter-bar {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;
}
.filter-bar input,
.filter-bar select {
border-radius: 999px;
border: 1px solid #d1d5db;
padding: 6px 10px;
font-size: 13px;
}
.layout {
margin-top: 16px;
display: grid;
grid-template-columns: minmax(0, 1.4fr) minmax(0, 1.2fr);
gap: 16px;
}
@media (max-width: 900px) {
.layout {
grid-template-columns: 1fr;
}
}
.job-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.job-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
border-radius: 10px;
background: #ffffff;
border: 1px solid #e5e7eb;
cursor: pointer;
}
.job-item.active {
border-color: #2563eb;
box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.3);
}
.job-title-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
align-items: baseline;
}
.job-title {
font-size: 15px;
font-weight: 500;
}
.job-company {
font-size: 13px;
color: #6b7280;
}
.job-meta {
margin-top: 4px;
display: flex;
flex-wrap: wrap;
gap: 8px;
font-size: 12px;
color: #6b7280;
}
.job-actions {
display: flex;
flex-direction: column;
gap: 6px;
margin-left: 10px;
}
button {
border-radius: 999px;
border: none;
padding: 5px 12px;
font-size: 12px;
cursor: pointer;
background: #2563eb;
color: #ffffff;
}
button:hover {
background: #1d4ed8;
}
.primary {
background: #f97316;
}
.primary:hover {
background: #ea580c;
}
.secondary {
background: #e5e7eb;
color: #374151;
}
.detail-card {
min-height: 260px;
}
.detail h4 {
margin: 0 0 4px;
font-size: 16px;
}
.detail-sub {
margin: 0 0 10px;
font-size: 13px;
color: #6b7280;
}
.detail h5 {
margin: 10px 0 6px;
font-size: 14px;
}
.detail ul {
padding-left: 18px;
margin: 0;
font-size: 13px;
color: #4b5563;
}
.detail-desc {
font-size: 13px;
color: #4b5563;
margin: 0;
}
.empty-text {
margin-top: 6px;
font-size: 13px;
color: #9ca3af;
}
</style>

View File

@@ -1,24 +1,398 @@
<template>
<div class="page">
<h2>系统设置</h2>
<p>这里可以配置系统参数主题用户等</p>
<h2>求职辅助工具模块</h2>
<p class="intro">
通过测评经验库与行业资讯为理工科毕业生提供更全面的求职决策支持
</p>
<section class="card">
<h3>1. 能力测评工具</h3>
<div class="assessment-grid">
<div
class="assessment-card"
:class="{ active: activeAssessment === 'skill' }"
@click="setAssessment('skill')"
>
<h4>专业技能测评</h4>
<p>评估编程工程设计数据分析等理工科核心技能掌握情况</p>
<button type="button">开始测评</button>
</div>
<div
class="assessment-card"
:class="{ active: activeAssessment === 'personality' }"
@click="setAssessment('personality')"
>
<h4>职业性格测评</h4>
<p>了解更适合你的岗位类型与团队环境辅助职业定位</p>
<button type="button">开始测评</button>
</div>
</div>
<div v-if="activeAssessment" class="assessment-panel">
<h4>测评问题示例{{ activeAssessmentLabel }}</h4>
<p class="question">
Q1你更倾向于哪种工作内容
</p>
<ul class="options">
<li><label><input type="radio" name="q1" /> A. 深度编码与技术攻关</label></li>
<li>
<label><input type="radio" name="q1" /> B. 与人沟通需求分析与协调</label>
</li>
<li>
<label><input type="radio" name="q1" /> C. 数据分析建模与实验验证</label>
</li>
</ul>
<button type="button" class="submit-btn">提交测评示例</button>
</div>
</section>
<section class="card">
<h3>2. 面试经验库</h3>
<div class="experience-filter">
<input v-model="experienceKeyword" placeholder="按岗位搜索面试经验,例如:测试 / 算法" />
</div>
<div class="experience-list">
<div
v-for="item in filteredExperiences"
:key="item.id"
class="experience-item"
>
<div>
<div class="exp-title">
{{ item.position }} · {{ item.company }}
</div>
<div class="exp-tags">
<span class="tag" v-for="tag in item.tags" :key="tag">
{{ tag }}
</span>
</div>
<p class="exp-brief">{{ item.brief }}</p>
</div>
<button type="button" class="secondary-btn">查看详情</button>
</div>
<p v-if="!filteredExperiences.length" class="empty-text">
暂无相关面试经验可以尝试更换关键词
</p>
</div>
</section>
<section class="card">
<h3>3. 就业资讯</h3>
<div class="news-list">
<div v-for="news in newsList" :key="news.id" class="news-item">
<div class="news-main">
<div class="news-title">{{ news.title }}</div>
<div class="news-meta">
<span>{{ news.tag }}</span>
<span>{{ news.date }}</span>
</div>
</div>
<button type="button" class="link-btn">查看详情</button>
</div>
</div>
</section>
</div>
</template>
<script setup lang="ts"></script>
<script setup lang="ts">
import { computed, ref } from 'vue'
type AssessmentType = 'skill' | 'personality' | null
type ExperienceItem = {
id: number
position: string
company: string
tags: string[]
brief: string
}
type NewsItem = {
id: number
title: string
tag: string
date: string
}
const activeAssessment = ref<AssessmentType>(null)
const experienceKeyword = ref('')
const experiences = ref<ExperienceItem[]>([
{
id: 1,
position: '后端开发工程师',
company: '互联网大厂一面 + 二面',
tags: ['Java', '计网', '操作系统'],
brief: '主要考察基础算法、项目细节以及高并发相关问题。'
},
{
id: 2,
position: '测试开发工程师',
company: '智能设备公司现场面试',
tags: ['测试用例', 'Python', '自动化测试'],
brief: '以场景题为主,需要设计完整的测试方案和脚本思路。'
},
{
id: 3,
position: '算法工程师实习',
company: 'AI 创业公司线上面试',
tags: ['机器学习', '数学基础', '项目经历'],
brief: '注重项目中模型选择理由以及效果对比。'
}
])
const newsList = ref<NewsItem[]>([
{
id: 1,
title: '2026 届理工科毕业生就业形势分析报告',
tag: '行业报告',
date: '2026-01-15'
},
{
id: 2,
title: '国家发布关于促进高校毕业生就业的新政策',
tag: '政策解读',
date: '2025-12-30'
},
{
id: 3,
title: '多家头部科技企业启动春季校园招聘',
tag: '招聘动态',
date: '2026-02-01'
}
])
const activeAssessmentLabel = computed(() => {
if (activeAssessment.value === 'skill') return '专业技能测评'
if (activeAssessment.value === 'personality') return '职业性格测评'
return ''
})
const filteredExperiences = computed(() => {
if (!experienceKeyword.value.trim()) return experiences.value
return experiences.value.filter(
(item) =>
item.position.includes(experienceKeyword.value) ||
item.company.includes(experienceKeyword.value) ||
item.tags.some((t) => t.includes(experienceKeyword.value))
)
})
function setAssessment(type: AssessmentType) {
activeAssessment.value = type
}
</script>
<style scoped>
.page h2 {
margin-top: 0;
margin-bottom: 8px;
font-size: 18px;
font-size: 20px;
font-weight: 600;
}
.page p {
margin: 0;
.intro {
margin: 0 0 16px;
color: #4b5563;
font-size: 14px;
}
.card {
background: #f9fafb;
border-radius: 12px;
padding: 14px 16px;
border: 1px solid #e5e7eb;
margin-bottom: 14px;
}
.assessment-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
margin-top: 8px;
}
.assessment-card {
padding: 10px 12px;
border-radius: 10px;
background: #ffffff;
border: 1px solid #e5e7eb;
cursor: pointer;
}
.assessment-card.active {
border-color: #2563eb;
box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.3);
}
.assessment-card h4 {
margin: 0 0 6px;
font-size: 15px;
}
.assessment-card p {
margin: 0 0 8px;
font-size: 13px;
color: #4b5563;
}
.assessment-card button {
border-radius: 999px;
border: none;
padding: 5px 12px;
font-size: 12px;
cursor: pointer;
background: #2563eb;
color: #ffffff;
}
.assessment-panel {
margin-top: 12px;
padding: 10px 12px;
border-radius: 10px;
background: #eff6ff;
}
.assessment-panel h4 {
margin: 0 0 6px;
font-size: 14px;
}
.question {
margin: 0 0 6px;
font-size: 13px;
}
.options {
list-style: none;
padding: 0;
margin: 0 0 8px;
font-size: 13px;
}
.options li + li {
margin-top: 4px;
}
.submit-btn {
border-radius: 999px;
border: none;
padding: 5px 12px;
font-size: 12px;
cursor: pointer;
background: #f97316;
color: #ffffff;
}
.experience-filter {
margin-top: 8px;
}
.experience-filter input {
width: 100%;
border-radius: 999px;
border: 1px solid #d1d5db;
padding: 6px 10px;
font-size: 13px;
}
.experience-list {
margin-top: 10px;
display: flex;
flex-direction: column;
gap: 8px;
}
.experience-item {
padding: 10px 12px;
border-radius: 10px;
background: #ffffff;
border: 1px solid #e5e7eb;
display: flex;
justify-content: space-between;
gap: 10px;
align-items: center;
}
.exp-title {
font-size: 14px;
font-weight: 500;
}
.exp-tags {
margin-top: 4px;
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.tag {
font-size: 11px;
padding: 2px 6px;
border-radius: 999px;
background: #e0f2fe;
color: #0369a1;
}
.exp-brief {
margin: 4px 0 0;
font-size: 12px;
color: #6b7280;
}
.secondary-btn {
border-radius: 999px;
border: none;
padding: 5px 12px;
font-size: 12px;
cursor: pointer;
background: #e5e7eb;
color: #374151;
white-space: nowrap;
}
.news-list {
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 8px;
}
.news-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
border-radius: 10px;
background: #ffffff;
border: 1px solid #e5e7eb;
}
.news-title {
font-size: 14px;
}
.news-meta {
margin-top: 2px;
font-size: 12px;
color: #6b7280;
display: flex;
gap: 8px;
}
.link-btn {
border-radius: 999px;
border: none;
padding: 5px 10px;
font-size: 12px;
cursor: pointer;
background: #2563eb;
color: #ffffff;
}
.empty-text {
font-size: 13px;
color: #9ca3af;
}
</style>