feat:更新布局样式
This commit is contained in:
5
Dockerfile
Normal file
5
Dockerfile
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
FROM nginx:1.27.0
|
||||||
|
COPY dist /usr/share/nginx/html
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
|
||||||
|
RUN echo 'Asia/Shanghai' > /etc/timezone
|
||||||
8
nginx.conf
Normal file
8
nginx.conf
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
# 设置根目录
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
}
|
||||||
75
src/App.vue
75
src/App.vue
@@ -1,10 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="app-container">
|
<div class="app-container">
|
||||||
<!-- 导航栏 -->
|
<navbar />
|
||||||
<navbar :scroll-position="scrollPosition" @scroll-to-section="scrollToSection"></navbar>
|
|
||||||
|
|
||||||
<!-- 英雄区域 -->
|
<!-- 英雄区域 -->
|
||||||
<hero-section @scroll-to-section="scrollToSection"></hero-section>
|
<hero-section />
|
||||||
|
|
||||||
<!-- 技能部分 -->
|
<!-- 技能部分 -->
|
||||||
<skills-section section-id="skills"></skills-section>
|
<skills-section section-id="skills"></skills-section>
|
||||||
@@ -27,7 +26,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
|
||||||
import Navbar from './components/Navbar.vue'
|
import Navbar from './components/Navbar.vue'
|
||||||
import HeroSection from './components/HeroSection.vue'
|
import HeroSection from './components/HeroSection.vue'
|
||||||
import SkillsSection from './components/SkillsSection.vue'
|
import SkillsSection from './components/SkillsSection.vue'
|
||||||
@@ -36,30 +34,6 @@ import ProjectSection from '@/components/Project/ProjectSection.vue'
|
|||||||
import EducationSection from './components/Education/EducationSection.vue'
|
import EducationSection from './components/Education/EducationSection.vue'
|
||||||
import ContactSection from './components/Contact/ContactSection.vue'
|
import ContactSection from './components/Contact/ContactSection.vue'
|
||||||
import Footerbar from './components/Footerbar.vue'
|
import Footerbar from './components/Footerbar.vue'
|
||||||
|
|
||||||
// 滚动位置
|
|
||||||
const scrollPosition = ref(0)
|
|
||||||
|
|
||||||
// 监听滚动事件
|
|
||||||
onMounted(() => {
|
|
||||||
const handleScroll = () => {
|
|
||||||
scrollPosition.value = window.scrollY
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener('scroll', handleScroll)
|
|
||||||
return () => window.removeEventListener('scroll', handleScroll)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 滚动到指定区域
|
|
||||||
const scrollToSection = (sectionId) => {
|
|
||||||
const element = document.getElementById(sectionId)
|
|
||||||
if (element) {
|
|
||||||
window.scrollTo({
|
|
||||||
top: element.offsetTop - 80,
|
|
||||||
behavior: 'smooth'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@@ -69,6 +43,51 @@ const scrollToSection = (sectionId) => {
|
|||||||
.app-container {
|
.app-container {
|
||||||
color: #333;
|
color: #333;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
|
||||||
|
.light-section {
|
||||||
|
padding: 8rem 0;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-section {
|
||||||
|
padding: 8rem 0;
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.light-card {
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 2rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark-card {
|
||||||
|
background-color: #f9fafb;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 2rem;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-tag {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全局动画类
|
// 全局动画类
|
||||||
|
|||||||
@@ -31,22 +31,12 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="消息" prop="message">
|
<el-form-item label="消息" prop="message">
|
||||||
<el-input
|
<el-input v-model="formData.message" type="textarea" :rows="5" placeholder="请输入您的消息..."/>
|
||||||
v-model="formData.message"
|
|
||||||
type="textarea"
|
|
||||||
:rows="5"
|
|
||||||
placeholder="请输入您的消息..."
|
|
||||||
></el-input>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button
|
<el-button type="primary" size="large" :loading="isSubmitting"
|
||||||
type="primary"
|
@click="submitForm" class="submit-btn">
|
||||||
size="large"
|
|
||||||
:loading="isSubmitting"
|
|
||||||
@click="submitForm"
|
|
||||||
class="submit-btn"
|
|
||||||
>
|
|
||||||
<i class="fa fa-paper-plane mr-2"></i>
|
<i class="fa fa-paper-plane mr-2"></i>
|
||||||
<span v-if="!isSubmitting">发送消息</span>
|
<span v-if="!isSubmitting">发送消息</span>
|
||||||
<span v-if="isSubmitting">发送中...</span>
|
<span v-if="isSubmitting">发送中...</span>
|
||||||
@@ -54,11 +44,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item v-if="formSuccess">
|
<el-form-item v-if="formSuccess">
|
||||||
<el-alert
|
<el-alert title="消息发送成功!感谢您的联系,我会尽快回复您。" type="success" show-icon></el-alert>
|
||||||
title="消息发送成功!感谢您的联系,我会尽快回复您。"
|
|
||||||
type="success"
|
|
||||||
show-icon
|
|
||||||
></el-alert>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|||||||
@@ -6,11 +6,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info-list">
|
<div class="info-list">
|
||||||
<div
|
<div v-for="(info, index) in profileStore.contactInfo" :key="index" class="info-item">
|
||||||
v-for="(info, index) in profileStore.contactInfo"
|
|
||||||
:key="index"
|
|
||||||
class="info-item"
|
|
||||||
>
|
|
||||||
<div class="info-icon">
|
<div class="info-icon">
|
||||||
<i :class="`fa ${info.icon}`"></i>
|
<i :class="`fa ${info.icon}`"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<section id="contact" class="contact-section">
|
<section id="contact" class="contact-section light-section">
|
||||||
<div class="container">
|
<div class="section-container">
|
||||||
<div class="section-title animate__animated animate__fadeIn">
|
<div class="section-title animate__animated animate__fadeIn">
|
||||||
<h2>联系我</h2>
|
<h2>联系我</h2>
|
||||||
<p>如果您对我的技能和经验感兴趣,或者有任何合作机会,欢迎随时联系我。</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="contact-grid">
|
<div class="contact-grid">
|
||||||
@@ -19,20 +18,11 @@
|
|||||||
import ContactInfo from '@/components/Contact/ContactInfo.vue'
|
import ContactInfo from '@/components/Contact/ContactInfo.vue'
|
||||||
import ContactForm from '@/components/Contact/ContactForm.vue'
|
import ContactForm from '@/components/Contact/ContactForm.vue'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
|
import { getObserver } from '@/utils/index.ts'
|
||||||
|
|
||||||
// 监听滚动,实现元素进入视口时的动画
|
// 监听滚动,实现元素进入视口时的动画
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const observer = new IntersectionObserver((entries) => {
|
const observer = getObserver()
|
||||||
entries.forEach((entry) => {
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
const delay = entry.target.getAttribute('data-delay')
|
|
||||||
setTimeout(() => {
|
|
||||||
entry.target.classList.add('animate-visible', 'animate__animated', 'animate__fadeInUp')
|
|
||||||
}, delay ? parseInt(delay) : 0)
|
|
||||||
observer.unobserve(entry.target)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, { threshold: 0.1 })
|
|
||||||
|
|
||||||
// 观察联系信息和表单
|
// 观察联系信息和表单
|
||||||
document.querySelectorAll('.contact-info, .contact-form').forEach((item) => {
|
document.querySelectorAll('.contact-info, .contact-form').forEach((item) => {
|
||||||
@@ -45,14 +35,6 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.contact-section {
|
.contact-section {
|
||||||
padding: 1rem 0;
|
|
||||||
background-color: #FFFFFF;
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 1rem;
|
|
||||||
|
|
||||||
.contact-grid {
|
.contact-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -62,8 +44,6 @@ onMounted(() => {
|
|||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.el-card {
|
.el-card {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@@ -78,9 +58,8 @@ onMounted(() => {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
.card-header {
|
.card-header {
|
||||||
background-color: #f9fafb;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid #e5e7eb;
|
||||||
padding: 1rem;
|
padding: 1rem 0;
|
||||||
|
|
||||||
h3 {
|
h3 {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -100,18 +79,13 @@ onMounted(() => {
|
|||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-around;
|
||||||
|
gap: 10px;
|
||||||
margin-bottom: 2rem;
|
|
||||||
|
|
||||||
.info-item {
|
.info-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 1.5rem;
|
gap: 20px;
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-icon {
|
.info-icon {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
@@ -121,7 +95,6 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-right: 1rem;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|
||||||
i {
|
i {
|
||||||
@@ -132,7 +105,7 @@ onMounted(() => {
|
|||||||
.info-content {
|
.info-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
gap: 5px;
|
||||||
|
|
||||||
.info-title {
|
.info-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
@@ -147,43 +120,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.social-links-section {
|
|
||||||
border-top: 1px solid #e5e7eb;
|
|
||||||
padding-top: 1.5rem;
|
|
||||||
|
|
||||||
.social-title {
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-links {
|
|
||||||
display: flex;
|
|
||||||
gap: 1rem;
|
|
||||||
|
|
||||||
.social-link {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: white;
|
|
||||||
color: #666;
|
|
||||||
text-decoration: none;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: #409EFF;
|
|
||||||
color: white;
|
|
||||||
transform: translateY(-3px);
|
|
||||||
box-shadow: 0 6px 15px rgba(64, 158, 255, 0.3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.contact-form {
|
.contact-form {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="education-card animate-item">
|
<div class="education-card light-card animate-item">
|
||||||
<div class="education-header">
|
<div class="education-header">
|
||||||
<div class="education-info">
|
<div class="education-info">
|
||||||
<span class="period">{{ item.period }}</span>
|
<span class="period">{{ item.period }}</span>
|
||||||
<h3 class="degree">{{ item.degree }}</h3>
|
<span class="degree">{{ item.degree }}</span>
|
||||||
<h4 class="school">{{ item.school }}</h4>
|
<span class="school">{{ item.school }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="education-icon">
|
<div class="education-icon">
|
||||||
<i class="fa fa-graduation-cap"></i>
|
<i class="fa fa-graduation-cap"></i>
|
||||||
@@ -15,13 +15,8 @@
|
|||||||
|
|
||||||
<div class="education-details">
|
<div class="education-details">
|
||||||
<h4 class="details-title">{{ item.detailsTitle }}:</h4>
|
<h4 class="details-title">{{ item.detailsTitle }}:</h4>
|
||||||
<div class="details-tags">
|
<div class="section-tag">
|
||||||
<el-tag
|
<el-tag v-for="(detail, i) in item.details" :key="i" type="primary">
|
||||||
v-for="(detail, i) in item.details"
|
|
||||||
:key="i"
|
|
||||||
type="warning"
|
|
||||||
effect="light"
|
|
||||||
>
|
|
||||||
{{ detail }}
|
{{ detail }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<section id="education" class="education-section">
|
<section id="education" class="education-section dark-section">
|
||||||
<div class="container">
|
<div class="section-container">
|
||||||
<div class="section-title animate__animated animate__fadeIn">
|
<div class="section-title animate__animated animate__fadeIn">
|
||||||
<h2>教育背景</h2>
|
<h2>教育背景</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,15 +37,6 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.education-section {
|
.education-section {
|
||||||
padding: 1rem 0;
|
|
||||||
background-color: #f9fafb;
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.education-grid {
|
.education-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -57,16 +48,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.education-card {
|
.education-card {
|
||||||
background-color: white;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 2rem;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.education-header {
|
.education-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@@ -74,25 +55,25 @@ onMounted(() => {
|
|||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
.education-info {
|
.education-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
.period {
|
.period {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 0.875rem;
|
|
||||||
color: #409EFF;
|
color: #409EFF;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.degree {
|
.degree {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin: 0 0 0.25rem 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.school {
|
.school {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,24 +96,15 @@ onMounted(() => {
|
|||||||
.description {
|
.description {
|
||||||
color: #666;
|
color: #666;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.education-details {
|
.education-details {
|
||||||
border-top: 1px solid #e5e7eb;
|
border-top: 1px solid #e5e7eb;
|
||||||
padding-top: 1.5rem;
|
|
||||||
|
|
||||||
.details-title {
|
.details-title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 1rem;
|
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.details-tags {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="timeline-item animate-item">
|
<div class="timeline-item animate-item">
|
||||||
<!-- 时间点标记 -->
|
|
||||||
<div class="timeline-dot">
|
<div class="timeline-dot">
|
||||||
<i class="fa fa-briefcase"></i>
|
<i class="fa fa-briefcase"></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 内容容器 -->
|
|
||||||
<div class="timeline-content">
|
<div class="timeline-content">
|
||||||
<div class="experience-header">
|
<div class="experience-header">
|
||||||
<span class="period">{{ item.period }}</span>
|
<span class="period">{{ item.period }}</span>
|
||||||
<h3 class="position">{{ item.position }}</h3>
|
<span class="position">{{ item.position }}</span>
|
||||||
<h4 class="company">{{ item.company }}</h4>
|
<span class="company">{{ item.company }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="description">{{ item.description }}</p>
|
<p class="description">{{ item.description }}</p>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<section id="experience" class="experience-section">
|
<section id="experience" class="experience-section light-section">
|
||||||
<div class="container">
|
<div class="section-container">
|
||||||
<div class="section-title animate__animated animate__fadeIn">
|
<div class="section-title animate__animated animate__fadeIn">
|
||||||
<h2>工作经验</h2>
|
<h2>工作经验</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,18 +42,8 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.experience-section {
|
.experience-section {
|
||||||
padding: 1rem 0;
|
|
||||||
background-color: #FFFFFF;
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline {
|
.timeline {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 3rem;
|
|
||||||
|
|
||||||
.timeline-axis {
|
.timeline-axis {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -143,43 +133,33 @@ onMounted(() => {
|
|||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
.experience-header {
|
.experience-header {
|
||||||
margin-bottom: 1rem;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
.period {
|
.period {
|
||||||
display: inline-block;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
color: #409EFF;
|
color: #409EFF;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.position {
|
.position {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.company {
|
.company {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
color: #666;
|
color: #666;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
margin-bottom: 1rem;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
text-indent: 2em;
|
text-indent: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.experience-tags {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<footer class="footer">
|
<footer class="footer">
|
||||||
<div class="container">
|
<div class="section-container">
|
||||||
<div class="footer-content">
|
<div class="footer-content">
|
||||||
<div class="footer-brand">
|
<div class="footer-brand">
|
||||||
<a href="#" class="logo">
|
<a href="#" class="logo">
|
||||||
Resume<span class="logo-highlight">.</span>
|
Resume<span class="logo-highlight">.</span>
|
||||||
</a>
|
</a>
|
||||||
<p class="brand-description">专业前端工程师的个人简历</p>
|
<p class="brand-description">个人简历</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer-copyright">
|
<div class="footer-copyright">
|
||||||
<p>© {{ new Date().getFullYear() }} {{ profileStore.personalInfo.name }}. 保留所有权利.</p>
|
<p>© {{ new Date().getFullYear() }} {{ profileStore.personalInfo.name }}</p>
|
||||||
<p class="footer-credit">设计与开发 <i class="fa fa-heart"></i></p>
|
<p class="footer-credit">设计与开发 <i class="fa fa-heart"></i></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -28,13 +28,7 @@ const profileStore = useProfileStore()
|
|||||||
.footer {
|
.footer {
|
||||||
background-color: #1d2129;
|
background-color: #1d2129;
|
||||||
color: #86909c;
|
color: #86909c;
|
||||||
padding: 4rem 0 2rem;
|
padding: 2rem 0 2rem;
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-content {
|
.footer-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -31,18 +31,11 @@
|
|||||||
|
|
||||||
<!-- 按钮 -->
|
<!-- 按钮 -->
|
||||||
<div class="action-buttons">
|
<div class="action-buttons">
|
||||||
<el-button
|
<el-button type="primary" @click="scrollToSection('contact')"
|
||||||
type="primary"
|
class="animate__animated animate__fadeInUp animate__delay-500">
|
||||||
size="large"
|
|
||||||
@click="$emit('scroll-to-section', 'contact')"
|
|
||||||
class="animate__animated animate__fadeInUp animate__delay-500"
|
|
||||||
>
|
|
||||||
<i class="fa fa-paper-plane mr-2"></i> 联系我
|
<i class="fa fa-paper-plane mr-2"></i> 联系我
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button class="ml-4 animate__animated animate__fadeInUp animate__delay-700">
|
||||||
size="large"
|
|
||||||
class="ml-4 animate__animated animate__fadeInUp animate__delay-700"
|
|
||||||
>
|
|
||||||
<i class="fa fa-download mr-2"></i> 下载简历
|
<i class="fa fa-download mr-2"></i> 下载简历
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,13 +46,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { getAssetsImageFile } from '@/utils/index.js'
|
import { getAssetsImageFile, scrollToSection } from '@/utils/index.js'
|
||||||
import { useProfileStore } from '@/store/profile.js'
|
import { useProfileStore } from '@/store/profile.js'
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
|
|
||||||
// 定义事件
|
|
||||||
const emits = defineEmits(['scroll-to-section'])
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -71,7 +61,6 @@ const emits = defineEmits(['scroll-to-section'])
|
|||||||
.container {
|
.container {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-content {
|
.hero-content {
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<header
|
<header class="navbar">
|
||||||
class="navbar"
|
|
||||||
:class="{ 'navbar-scrolled': scrollPosition > 50 }"
|
|
||||||
>
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="navbar-content">
|
<div class="navbar-content">
|
||||||
<a href="#" class="logo">
|
<a href="#" class="logo">
|
||||||
@@ -12,29 +9,22 @@
|
|||||||
<!-- 桌面导航 -->
|
<!-- 桌面导航 -->
|
||||||
<nav class="desktop-nav">
|
<nav class="desktop-nav">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#about" @click.prevent="$emit('scroll-to-section', 'about')">关于我</a></li>
|
<li><a href="#about" @click.prevent="scrollToSection('about')">关于我</a></li>
|
||||||
<li><a href="#skills" @click.prevent="$emit('scroll-to-section', 'skills')">技能</a></li>
|
<li><a href="#skills" @click.prevent="scrollToSection('skills')">技能</a></li>
|
||||||
<li><a href="#education" @click.prevent="$emit('scroll-to-section', 'education')">教育背景</a></li>
|
<li><a href="#education" @click.prevent="scrollToSection('education')">教育背景</a></li>
|
||||||
<li><a href="#experience" @click.prevent="$emit('scroll-to-section', 'experience')">工作经验</a></li>
|
<li><a href="#experience" @click.prevent="scrollToSection('experience')">工作经验</a></li>
|
||||||
<li><a href="#contact" @click.prevent="$emit('scroll-to-section', 'contact')">联系方式</a></li>
|
<li><a href="#contact" @click.prevent="scrollToSection('contact')">联系方式</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<!-- 移动端菜单按钮 -->
|
<!-- 移动端菜单按钮 -->
|
||||||
<button
|
<el-button type="primary" @click="mobileMenuOpen = !mobileMenuOpen" class="mobile-menu-btn">
|
||||||
class="mobile-menu-btn"
|
|
||||||
@click="mobileMenuOpen = !mobileMenuOpen"
|
|
||||||
aria-label="菜单"
|
|
||||||
>
|
|
||||||
<i class="fa" :class="mobileMenuOpen ? 'fa-times' : 'fa-bars'"></i>
|
<i class="fa" :class="mobileMenuOpen ? 'fa-times' : 'fa-bars'"></i>
|
||||||
</button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 移动端导航菜单 -->
|
<!-- 移动端导航菜单 -->
|
||||||
<div
|
<div class="mobile-nav" :class="{ 'mobile-nav-open': mobileMenuOpen }">
|
||||||
class="mobile-nav"
|
|
||||||
:class="{ 'mobile-nav-open': mobileMenuOpen }"
|
|
||||||
>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="#about" @click.prevent="handleScroll('about')">关于我</a></li>
|
<li><a href="#about" @click.prevent="handleScroll('about')">关于我</a></li>
|
||||||
<li><a href="#skills" @click.prevent="handleScroll('skills')">技能</a></li>
|
<li><a href="#skills" @click.prevent="handleScroll('skills')">技能</a></li>
|
||||||
@@ -48,15 +38,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, defineProps } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { scrollToSection } from '@/utils/index.ts'
|
||||||
// 接收滚动位置属性
|
|
||||||
const props = defineProps({
|
|
||||||
scrollPosition: {
|
|
||||||
type: Number,
|
|
||||||
default: 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 移动端菜单状态
|
// 移动端菜单状态
|
||||||
const mobileMenuOpen = ref(false)
|
const mobileMenuOpen = ref(false)
|
||||||
@@ -64,11 +47,8 @@ const mobileMenuOpen = ref(false)
|
|||||||
// 处理滚动并关闭菜单
|
// 处理滚动并关闭菜单
|
||||||
const handleScroll = (sectionId) => {
|
const handleScroll = (sectionId) => {
|
||||||
mobileMenuOpen.value = false
|
mobileMenuOpen.value = false
|
||||||
emits('scroll-to-section', sectionId)
|
scrollToSection(sectionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义事件
|
|
||||||
const emits = defineEmits(['scroll-to-section'])
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -82,22 +62,16 @@ const emits = defineEmits(['scroll-to-section'])
|
|||||||
background-color: rgba(255, 255, 255, 0.95);
|
background-color: rgba(255, 255, 255, 0.95);
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
&.navbar-scrolled {
|
|
||||||
padding: 0.5rem 0;
|
|
||||||
box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
max-width: 1200px;
|
max-width: 1200px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-content {
|
.navbar-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 1rem 0;
|
padding: 10px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
@@ -105,10 +79,6 @@ const emits = defineEmits(['scroll-to-section'])
|
|||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #333;
|
color: #333;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
.logo-highlight {
|
|
||||||
color: #409EFF;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop-nav {
|
.desktop-nav {
|
||||||
@@ -139,11 +109,6 @@ const emits = defineEmits(['scroll-to-section'])
|
|||||||
|
|
||||||
.mobile-menu-btn {
|
.mobile-menu-btn {
|
||||||
display: none;
|
display: none;
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: #666;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-nav {
|
.mobile-nav {
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
<div class="timeline-content">
|
<div class="timeline-content">
|
||||||
<div class="project-header">
|
<div class="project-header">
|
||||||
<span class="period">{{ props.item.period }}</span>
|
<span class="period">{{ props.item.period }}</span>
|
||||||
<h3 class="name">{{ props.item.name }}</h3>
|
<span class="name">{{ props.item.name }}</span>
|
||||||
<h4 class="role">{{ props.item.role }}</h4>
|
<span class="role">{{ props.item.role }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="description">{{ props.item.description }}</p>
|
<p class="description">{{ props.item.description }}</p>
|
||||||
@@ -22,8 +22,8 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="project-tags">
|
<div class="section-tag">
|
||||||
<el-tag v-for="(tag, index) in props.item.tags" :key="index" effect="light">
|
<el-tag v-for="(tag, index) in props.item.tags" :key="index">
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<section id="projects" class="projects-section">
|
<section id="projects" class="projects-section dark-section">
|
||||||
<div class="container">
|
<div class="section-container">
|
||||||
<div class="section-title animate__animated animate__fadeIn">
|
<div class="section-title animate__animated animate__fadeIn">
|
||||||
<h2>项目经历</h2>
|
<h2>项目经历</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,15 +42,6 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.projects-section {
|
.projects-section {
|
||||||
padding: 1rem 0;
|
|
||||||
background-color: #f9fafb;
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline {
|
.timeline {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 3rem;
|
margin-top: 3rem;
|
||||||
@@ -137,7 +128,7 @@ onMounted(() => {
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #67C23A;
|
background-color: #409EFF;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@@ -161,27 +152,25 @@ onMounted(() => {
|
|||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
.project-header {
|
.project-header {
|
||||||
margin-bottom: 1rem;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
.period {
|
.period {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 0.875rem;
|
color: #409EFF;
|
||||||
color: #67C23A;
|
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.name {
|
.name {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.role {
|
.role {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #666;
|
color: #666;
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,12 +200,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-tags {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-image {
|
.project-image {
|
||||||
|
|||||||
@@ -1,29 +1,20 @@
|
|||||||
<template>
|
<template>
|
||||||
<section :id="sectionId" class="skills-section">
|
<section :id="sectionId" class="skills-section light-section">
|
||||||
<div class="container">
|
<div class="section-container">
|
||||||
<div class="section-title animate__animated animate__fadeIn">
|
<div class="section-title animate__animated animate__fadeIn">
|
||||||
<h2>专业技能</h2>
|
<h2>专业技能</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="skills-grid">
|
<div class="skills-grid">
|
||||||
<div
|
<div v-for="(skill, index) in profileStore.skills" :key="index"
|
||||||
v-for="(skill, index) in profileStore.skills"
|
class="skill-card dark-card animate-item" :data-delay="index * 150">
|
||||||
:key="index"
|
|
||||||
class="skill-card animate-item"
|
|
||||||
:data-delay="index * 150"
|
|
||||||
>
|
|
||||||
<div class="skill-icon">
|
<div class="skill-icon">
|
||||||
<i :class="`fa ${skill.icon}`"></i>
|
<i :class="`fa ${skill.icon}`"></i>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="skill-title">{{ skill.title }}</h3>
|
<h3 class="skill-title">{{ skill.title }}</h3>
|
||||||
<p class="skill-description">{{ skill.description }}</p>
|
<p class="skill-description">{{ skill.description }}</p>
|
||||||
<div class="skill-tags">
|
<div class="section-tag">
|
||||||
<el-tag
|
<el-tag v-for="(tag, i) in skill.tags" :key="i" type="info">
|
||||||
v-for="(tag, i) in skill.tags"
|
|
||||||
:key="i"
|
|
||||||
type="info"
|
|
||||||
effect="light"
|
|
||||||
>
|
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
@@ -36,6 +27,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, onMounted } from 'vue'
|
import { defineProps, onMounted } from 'vue'
|
||||||
import { useProfileStore } from '@/store/profile.ts'
|
import { useProfileStore } from '@/store/profile.ts'
|
||||||
|
import { getObserver } from '@/utils/index.ts'
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
|
|
||||||
@@ -48,22 +40,7 @@ const props = defineProps({
|
|||||||
|
|
||||||
// 监听滚动,实现元素进入视口时的动画,离开时移除动画
|
// 监听滚动,实现元素进入视口时的动画,离开时移除动画
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const observer = new IntersectionObserver((entries) => {
|
const observer = getObserver()
|
||||||
entries.forEach((entry) => {
|
|
||||||
const delay = entry.target.getAttribute('data-delay')
|
|
||||||
const delayTime = delay ? parseInt(delay) : 0
|
|
||||||
|
|
||||||
if (entry.isIntersecting) {
|
|
||||||
// 元素进入视口,添加动画类
|
|
||||||
setTimeout(() => {
|
|
||||||
entry.target.classList.add('animate-visible', 'animate__animated', 'animate__fadeInUp')
|
|
||||||
}, delayTime)
|
|
||||||
} else {
|
|
||||||
// 元素离开视口,移除动画类(以便下次进入时重新触发)
|
|
||||||
// entry.target.classList.remove('animate-visible', 'animate__animated', 'animate__fadeInUp')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, { threshold: 0.1 })
|
|
||||||
|
|
||||||
// 观察所有技能卡片
|
// 观察所有技能卡片
|
||||||
document.querySelectorAll('.skill-card').forEach((card) => {
|
document.querySelectorAll('.skill-card').forEach((card) => {
|
||||||
@@ -76,17 +53,6 @@ onMounted(() => {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.skills-section {
|
.skills-section {
|
||||||
padding: 8rem 0;
|
|
||||||
background-color: white;
|
|
||||||
/* 增加高度让滚动效果更明显 */
|
|
||||||
min-height: 100vh;
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skills-grid {
|
.skills-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@@ -102,24 +68,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.skill-card {
|
.skill-card {
|
||||||
background-color: #f9fafb;
|
|
||||||
border-radius: 10px;
|
|
||||||
padding: 2rem;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
/* 初始状态设置为不可见 */
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(20px);
|
|
||||||
|
|
||||||
&.animate-visible {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
transform: translateY(-5px);
|
|
||||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.skill-icon {
|
.skill-icon {
|
||||||
width: 60px;
|
width: 60px;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
@@ -128,7 +76,6 @@ onMounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
|
|
||||||
i {
|
i {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
@@ -139,21 +86,13 @@ onMounted(() => {
|
|||||||
.skill-title {
|
.skill-title {
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 1rem;
|
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skill-description {
|
.skill-description {
|
||||||
color: #666;
|
color: #666;
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.skill-tags {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ export const useProfileStore = defineStore('profile', {
|
|||||||
education: [
|
education: [
|
||||||
{
|
{
|
||||||
period: '2015年9月 - 2017年6月',
|
period: '2015年9月 - 2017年6月',
|
||||||
degree: '自动化专业 专科(偏软件方向)',
|
degree: '自动化专业',
|
||||||
school: '南京工业大学',
|
school: '南京工业大学',
|
||||||
description: '主修自动化专业,侧重软件与控制系统开发方向,系统学习数据结构、算法、计算机基础等核心课程,参与多个课程设计与项目实践。',
|
description: '主修自动化专业,侧重软件与控制系统开发方向,系统学习数据结构、算法、计算机基础等核心课程,参与多个课程设计与项目实践。',
|
||||||
detailsTitle: '主要课程',
|
detailsTitle: '主要课程',
|
||||||
|
|||||||
@@ -20,3 +20,14 @@ export const getObserver = () => {
|
|||||||
})
|
})
|
||||||
}, { threshold: 0.1 })
|
}, { threshold: 0.1 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 滚动到指定区域
|
||||||
|
export const scrollToSection = (sectionId: string) => {
|
||||||
|
const element = document.getElementById(sectionId)
|
||||||
|
if (element) {
|
||||||
|
window.scrollTo({
|
||||||
|
top: element.offsetTop - 80,
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user