feat:初始化工程
This commit is contained in:
5
.eslintrc.cjs
Normal file
5
.eslintrc.cjs
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'./node_modules/vue3-eslint'
|
||||
]
|
||||
}
|
||||
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3
.vscode/extensions.json
vendored
Normal file
3
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar"]
|
||||
}
|
||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Vue 3 + TypeScript + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
||||
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Cxx0822の简历</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
36
package.json
Normal file
36
package.json
Normal file
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "resume",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^6.7.2",
|
||||
"animate.css": "^4.1.1",
|
||||
"element-plus": "^2.9.7",
|
||||
"font-awesome": "^4.7.0",
|
||||
"vue": "^3.5.13",
|
||||
"vue3-common": "git+https://gitee.com/Cxx0822/vue3-common",
|
||||
"vue3-eslint": "git+https://gitee.com/Cxx0822/vue3-eslint"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.19.86",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.2.0",
|
||||
"eslint-plugin-vue": "^8.7.1",
|
||||
"sass": "^1.86.3",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.5",
|
||||
"vue-tsc": "^2.2.8"
|
||||
}
|
||||
}
|
||||
4198
pnpm-lock.yaml
generated
Normal file
4198
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
public/vite.svg
Normal file
1
public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
237
src/App.vue
Normal file
237
src/App.vue
Normal file
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<!-- 导航栏 -->
|
||||
<Navbar
|
||||
:scroll-position="scrollPosition"
|
||||
@scroll-to-section="scrollToSection"
|
||||
></Navbar>
|
||||
|
||||
<!-- 英雄区域 -->
|
||||
<HeroSection
|
||||
:personal-info="personalInfo"
|
||||
@scroll-to-section="scrollToSection"
|
||||
></HeroSection>
|
||||
|
||||
<!-- 技能部分 -->
|
||||
<SkillsSection
|
||||
:skills="skills"
|
||||
section-id="skills"
|
||||
></SkillsSection>
|
||||
|
||||
<!-- 工作经验 -->
|
||||
<ExperienceSection
|
||||
:experiences="experiences"
|
||||
section-id="experience"
|
||||
></ExperienceSection>
|
||||
|
||||
<!-- 教育背景 -->
|
||||
<EducationSection
|
||||
:education="education"
|
||||
section-id="education"
|
||||
></EducationSection>
|
||||
|
||||
<!-- 联系方式 -->
|
||||
<ContactSection
|
||||
:contact-info="contactInfo"
|
||||
:social-links="personalInfo.socials"
|
||||
section-id="contact"
|
||||
></ContactSection>
|
||||
|
||||
<!-- 页脚 -->
|
||||
<Footer
|
||||
:name="personalInfo.name"
|
||||
></Footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import Navbar from './components/Navbar.vue'
|
||||
import HeroSection from './components/HeroSection.vue'
|
||||
import SkillsSection from './components/SkillsSection.vue'
|
||||
import ExperienceSection from './components/ExperienceSection.vue'
|
||||
import EducationSection from './components/EducationSection.vue'
|
||||
import ContactSection from './components/ContactSection.vue'
|
||||
import Footer from './components/Footer.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'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 个人信息数据
|
||||
const personalInfo = {
|
||||
name: '陈修翔',
|
||||
title: '全栈工程师',
|
||||
description: '拥有5年前端开发经验,专注于构建响应式、高性能的现代Web应用,精通JavaScript、React和Vue等前端技术栈。',
|
||||
socials: [
|
||||
{ name: 'GitHub', icon: 'fa-brands fa-github', url: '#' },
|
||||
{ name: 'LinkedIn', icon: 'fa-brands fa-linkedin', url: '#' },
|
||||
{ name: 'Twitter', icon: 'fa-brands fa-twitter', url: '#' },
|
||||
{ name: 'Email', icon: 'fa-solid fa-envelope', url: '#' }
|
||||
]
|
||||
}
|
||||
|
||||
// 技能数据
|
||||
const skills = [
|
||||
{
|
||||
icon: 'fa-code',
|
||||
title: '前端开发',
|
||||
description: '精通HTML5、CSS3和JavaScript(ES6+),熟悉响应式设计和跨浏览器兼容性处理。',
|
||||
tags: ['HTML5', 'CSS3', 'JavaScript', 'TypeScript']
|
||||
},
|
||||
{
|
||||
icon: 'fa-react',
|
||||
title: '框架与库',
|
||||
description: '熟练使用React、Vue等主流前端框架,以及相关的状态管理和路由库。',
|
||||
tags: ['React', 'Vue', 'Redux', 'Vuex']
|
||||
},
|
||||
{
|
||||
icon: 'fa-paint-brush',
|
||||
title: 'UI/UX设计',
|
||||
description: '具备良好的设计感,熟悉主流UI组件库和设计系统,能够实现高质量的用户界面。',
|
||||
tags: ['Element UI', 'Ant Design', 'Figma', 'SCSS']
|
||||
},
|
||||
{
|
||||
icon: 'fa-cogs',
|
||||
title: '工程化',
|
||||
description: '熟悉前端工程化工具和流程,能够提高开发效率和代码质量。',
|
||||
tags: ['Webpack', 'Vite', 'Git', 'ESLint']
|
||||
},
|
||||
{
|
||||
icon: 'fa-server',
|
||||
title: '后端知识',
|
||||
description: '了解Node.js和常用后端技术,能够与后端团队高效协作,实现全栈开发。',
|
||||
tags: ['Node.js', 'Express', 'RESTful API', 'MongoDB']
|
||||
},
|
||||
{
|
||||
icon: 'fa-globe',
|
||||
title: '其他技能',
|
||||
description: '掌握项目管理和沟通技巧,具备良好的英语能力,能够阅读英文技术文档。',
|
||||
tags: ['敏捷开发', '英语', '团队协作', '问题解决']
|
||||
}
|
||||
]
|
||||
|
||||
// 工作经验数据
|
||||
const experiences = [
|
||||
{
|
||||
period: '2020年3月 - 至今',
|
||||
position: '高级前端工程师',
|
||||
company: '科技创新有限公司',
|
||||
description: '负责公司核心产品的前端架构设计和开发,优化前端性能,提升用户体验。主导多个大型项目的前端开发工作,带领5人团队完成产品迭代和维护。',
|
||||
tags: ['React', 'TypeScript', 'Redux'],
|
||||
image: 'https://picsum.photos/id/0/400/200'
|
||||
},
|
||||
{
|
||||
period: '2017年8月 - 2020年2月',
|
||||
position: '前端开发工程师',
|
||||
company: '互联网科技有限公司',
|
||||
description: '参与多个企业级Web应用的开发,负责前端页面实现和交互逻辑编写。与产品和设计团队紧密合作,将设计稿转化为高质量的代码,解决各种浏览器兼容性问题。',
|
||||
tags: ['Vue', 'SCSS', 'Webpack'],
|
||||
image: 'https://picsum.photos/id/180/400/200'
|
||||
},
|
||||
{
|
||||
period: '2016年6月 - 2017年7月',
|
||||
position: '前端开发实习生',
|
||||
company: '数字技术有限公司',
|
||||
description: '参与公司官网和内部管理系统的开发与维护,学习前端开发技术和最佳实践。协助 senior 工程师完成功能模块开发,修复已知bug,优化页面性能。',
|
||||
tags: ['HTML5', 'CSS3', 'jQuery'],
|
||||
image: 'https://picsum.photos/id/48/400/200'
|
||||
}
|
||||
]
|
||||
|
||||
// 教育背景数据
|
||||
const education = [
|
||||
{
|
||||
period: '2012年9月 - 2016年6月',
|
||||
degree: '计算机科学与技术',
|
||||
school: '北京大学',
|
||||
description: '本科期间主修计算机科学与技术专业,系统学习了数据结构、算法、计算机网络等基础知识,参与多个课程设计和科研项目。',
|
||||
detailsTitle: '主要课程',
|
||||
details: ['数据结构', '算法分析', '计算机网络', '操作系统', '数据库原理']
|
||||
},
|
||||
{
|
||||
period: '2016年9月 - 2018年6月',
|
||||
degree: '软件工程',
|
||||
school: '清华大学',
|
||||
description: '研究生阶段专注于软件工程领域,研究方向为Web前端技术和用户界面设计,发表2篇相关领域的学术论文,参与校企合作项目开发。',
|
||||
detailsTitle: '主要成果',
|
||||
details: ['学术论文2篇', '优秀毕业生', '校企合作项目', '奖学金']
|
||||
}
|
||||
]
|
||||
|
||||
// 联系信息数据
|
||||
const contactInfo = [
|
||||
{ icon: 'fa-map-marker', title: '地址', content: '北京市朝阳区建国路88号' },
|
||||
{ icon: 'fa-phone', title: '电话', content: '138-1234-5678' },
|
||||
{ icon: 'fa-envelope', title: '邮箱', content: 'liming@example.com' },
|
||||
{ icon: 'fa-clock-o', title: '工作时间', content: '周一至周五: 9:00 - 18:00' }
|
||||
]
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@use '@/styles/index.module.scss';
|
||||
|
||||
// 全局样式
|
||||
.app-container {
|
||||
color: #333;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
// 全局动画类
|
||||
.animate-item {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition: opacity 0.6s ease-out, transform 0.6s ease-out;
|
||||
}
|
||||
|
||||
.animate-visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
// 通用样式
|
||||
.section-title {
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
h2 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
p {
|
||||
color: #666;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式调整
|
||||
@media (max-width: 768px) {
|
||||
.section-title h2 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
BIN
src/assets/avatar.jpg
Normal file
BIN
src/assets/avatar.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 171 KiB |
BIN
src/assets/font/custom.woff2
Normal file
BIN
src/assets/font/custom.woff2
Normal file
Binary file not shown.
1
src/assets/vue.svg
Normal file
1
src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
370
src/components/ContactSection.vue
Normal file
370
src/components/ContactSection.vue
Normal file
@@ -0,0 +1,370 @@
|
||||
<template>
|
||||
<section :id="sectionId" class="contact-section">
|
||||
<div class="container">
|
||||
<div class="section-title animate__animated animate__fadeIn">
|
||||
<h2>联系我</h2>
|
||||
<p>如果您对我的技能和经验感兴趣,或者有任何合作机会,欢迎随时联系我。</p>
|
||||
</div>
|
||||
|
||||
<div class="contact-grid">
|
||||
<!-- 联系信息 -->
|
||||
<div class="contact-info animate-item" data-delay="100">
|
||||
<el-card>
|
||||
<div slot="header" class="card-header">
|
||||
<h3>联系方式</h3>
|
||||
</div>
|
||||
|
||||
<div class="info-list">
|
||||
<div
|
||||
v-for="(info, index) in contactInfo"
|
||||
:key="index"
|
||||
class="info-item"
|
||||
>
|
||||
<div class="info-icon">
|
||||
<i :class="`fa ${info.icon}`"></i>
|
||||
</div>
|
||||
<div class="info-content">
|
||||
<h4 class="info-title">{{ info.title }}</h4>
|
||||
<p class="info-text">{{ info.content }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="social-links-section">
|
||||
<h4 class="social-title">社交媒体</h4>
|
||||
<div class="social-links">
|
||||
<a
|
||||
v-for="(social, index) in socialLinks"
|
||||
:key="index"
|
||||
:href="social.url"
|
||||
:title="social.name"
|
||||
class="social-link"
|
||||
>
|
||||
<i :class="social.icon"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<!-- 联系表单 -->
|
||||
<div class="contact-form animate-item" data-delay="300">
|
||||
<el-card>
|
||||
<div slot="header" class="card-header">
|
||||
<h3>发送消息</h3>
|
||||
</div>
|
||||
|
||||
<el-form
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
ref="formRef"
|
||||
label-width="80px"
|
||||
class="message-form"
|
||||
@submit.prevent="submitForm"
|
||||
>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="24" :sm="12">
|
||||
<el-form-item label="姓名" prop="name">
|
||||
<el-input v-model="formData.name" placeholder="您的姓名"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24" :sm="12">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="formData.email" type="email" placeholder="您的邮箱"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<el-form-item label="主题" prop="subject">
|
||||
<el-input v-model="formData.subject" placeholder="消息主题"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="消息" prop="message">
|
||||
<el-input
|
||||
v-model="formData.message"
|
||||
type="textarea"
|
||||
:rows="5"
|
||||
placeholder="请输入您的消息..."
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="isSubmitting"
|
||||
@click="submitForm"
|
||||
class="submit-btn"
|
||||
>
|
||||
<i class="fa fa-paper-plane mr-2"></i>
|
||||
<span v-if="!isSubmitting">发送消息</span>
|
||||
<span v-if="isSubmitting">发送中...</span>
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="formSuccess">
|
||||
<el-alert
|
||||
title="消息发送成功!感谢您的联系,我会尽快回复您。"
|
||||
type="success"
|
||||
show-icon
|
||||
></el-alert>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, onMounted, reactive, ref } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
// 接收联系信息、社交链接和sectionId
|
||||
const props = defineProps({
|
||||
contactInfo: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
socialLinks: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
// 表单数据
|
||||
const formData = reactive({
|
||||
name: '',
|
||||
email: '',
|
||||
subject: '',
|
||||
message: ''
|
||||
})
|
||||
|
||||
// 表单规则
|
||||
const formRules = {
|
||||
name: [
|
||||
{ required: true, message: '请输入您的姓名', trigger: 'blur' }
|
||||
],
|
||||
email: [
|
||||
{ required: true, message: '请输入您的邮箱', trigger: 'blur' },
|
||||
{ type: 'email', message: '请输入有效的邮箱地址', trigger: 'blur' }
|
||||
],
|
||||
subject: [
|
||||
{ required: true, message: '请输入消息主题', trigger: 'blur' }
|
||||
],
|
||||
message: [
|
||||
{ required: true, message: '请输入您的消息', trigger: 'blur' },
|
||||
{ min: 10, message: '消息长度不能少于10个字符', trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
|
||||
// 表单引用
|
||||
const formRef = ref(null)
|
||||
|
||||
// 提交状态
|
||||
const isSubmitting = ref(false)
|
||||
const formSuccess = ref(false)
|
||||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
// 验证表单
|
||||
const valid = await formRef.value.validate()
|
||||
if (!valid) return
|
||||
|
||||
// 模拟提交
|
||||
isSubmitting.value = true
|
||||
formSuccess.value = false
|
||||
|
||||
try {
|
||||
// 模拟API请求延迟
|
||||
await new Promise((resolve) => setTimeout(resolve, 1500))
|
||||
|
||||
// 提交成功
|
||||
formSuccess.value = true
|
||||
|
||||
// 重置表单
|
||||
formRef.value.resetFields()
|
||||
|
||||
// 5秒后隐藏成功提示
|
||||
setTimeout(() => {
|
||||
formSuccess.value = false
|
||||
}, 5000)
|
||||
} catch (error) {
|
||||
ElMessage.error('提交失败,请稍后再试')
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 监听滚动,实现元素进入视口时的动画
|
||||
onMounted(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
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) => {
|
||||
observer.observe(item)
|
||||
})
|
||||
|
||||
return () => observer.disconnect()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.contact-section {
|
||||
padding: 8rem 0;
|
||||
background-color: #f9fafb;
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.contact-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.el-card {
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: #f9fafb;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding: 1.25rem 1.5rem;
|
||||
|
||||
h3 {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.el-card__body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.contact-info {
|
||||
.info-list {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.info-icon {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 1rem;
|
||||
flex-shrink: 0;
|
||||
|
||||
i {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
.info-content {
|
||||
.info-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
.message-form {
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 15px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
192
src/components/EducationSection.vue
Normal file
192
src/components/EducationSection.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<section :id="sectionId" class="education-section">
|
||||
<div class="container">
|
||||
<div class="section-title animate__animated animate__fadeIn">
|
||||
<h2>教育背景</h2>
|
||||
<p>系统的专业学习为我的技术生涯奠定了坚实的基础,培养了我的学习能力和问题解决能力。</p>
|
||||
</div>
|
||||
|
||||
<div class="education-grid">
|
||||
<div
|
||||
v-for="(edu, index) in education"
|
||||
:key="index"
|
||||
class="education-card animate-item"
|
||||
:data-delay="index * 300"
|
||||
>
|
||||
<div class="education-header">
|
||||
<div class="education-info">
|
||||
<span class="period">{{ edu.period }}</span>
|
||||
<h3 class="degree">{{ edu.degree }}</h3>
|
||||
<h4 class="school">{{ edu.school }}</h4>
|
||||
</div>
|
||||
<div class="education-icon">
|
||||
<i class="fa fa-graduation-cap"></i>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="description">{{ edu.description }}</p>
|
||||
|
||||
<div class="education-details">
|
||||
<h4 class="details-title">{{ edu.detailsTitle }}:</h4>
|
||||
<div class="details-tags">
|
||||
<el-tag
|
||||
v-for="(detail, i) in edu.details"
|
||||
:key="i"
|
||||
type="warning"
|
||||
effect="light"
|
||||
>
|
||||
{{ detail }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, onMounted } from 'vue'
|
||||
|
||||
// 接收教育数据和sectionId
|
||||
const props = defineProps({
|
||||
education: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
// 监听滚动,实现元素进入视口时的动画
|
||||
onMounted(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
const delay = entry.target.getAttribute('data-delay')
|
||||
const delayTime = delay ? parseInt(delay) : 0
|
||||
|
||||
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('.education-card').forEach((card) => {
|
||||
observer.observe(card)
|
||||
})
|
||||
|
||||
return () => observer.disconnect()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.education-section {
|
||||
padding: 8rem 0;
|
||||
background-color: white;
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.education-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.education-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);
|
||||
}
|
||||
|
||||
.education-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
.education-info {
|
||||
.period {
|
||||
display: inline-block;
|
||||
font-size: 0.875rem;
|
||||
color: #409EFF;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.degree {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
color: #333;
|
||||
margin: 0 0 0.25rem 0;
|
||||
}
|
||||
|
||||
.school {
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.education-icon {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 10px;
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
i {
|
||||
font-size: 1.25rem;
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.education-details {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
padding-top: 1.5rem;
|
||||
|
||||
.details-title {
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.details-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
272
src/components/ExperienceSection.vue
Normal file
272
src/components/ExperienceSection.vue
Normal file
@@ -0,0 +1,272 @@
|
||||
<template>
|
||||
<section :id="sectionId" class="experience-section">
|
||||
<div class="container">
|
||||
<div class="section-title animate__animated animate__fadeIn">
|
||||
<h2>工作经验</h2>
|
||||
<p>多年来在不同企业的工作经历,让我积累了丰富的项目经验和解决问题的能力。</p>
|
||||
</div>
|
||||
|
||||
<div class="timeline">
|
||||
<!-- 时间线中轴线 -->
|
||||
<div class="timeline-axis"></div>
|
||||
|
||||
<!-- 经验项 -->
|
||||
<div
|
||||
v-for="(exp, index) in experiences"
|
||||
:key="index"
|
||||
class="timeline-item animate-item"
|
||||
:class="{ 'timeline-item-left': index % 2 === 0, 'timeline-item-right': index % 2 === 1 }"
|
||||
:data-delay="index * 200"
|
||||
>
|
||||
<!-- 时间点标记 -->
|
||||
<div class="timeline-dot">
|
||||
<i class="fa fa-briefcase"></i>
|
||||
</div>
|
||||
|
||||
<!-- 内容容器 -->
|
||||
<div class="timeline-content">
|
||||
<div class="experience-header">
|
||||
<span class="period">{{ exp.period }}</span>
|
||||
<h3 class="position">{{ exp.position }}</h3>
|
||||
<h4 class="company">{{ exp.company }}</h4>
|
||||
</div>
|
||||
|
||||
<p class="description">{{ exp.description }}</p>
|
||||
|
||||
<div class="experience-tags">
|
||||
<el-tag
|
||||
v-for="(tag, i) in exp.tags"
|
||||
:key="i"
|
||||
type="success"
|
||||
effect="light"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 图片 -->
|
||||
<div class="timeline-image">
|
||||
<img :src="exp.image" :alt="exp.company + '项目图片'" class="experience-image">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, onMounted } from 'vue'
|
||||
|
||||
// 接收经验数据和sectionId
|
||||
const props = defineProps({
|
||||
experiences: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
// 监听滚动,实现元素进入视口时的动画
|
||||
onMounted(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
const delay = entry.target.getAttribute('data-delay')
|
||||
const delayTime = delay ? parseInt(delay) : 0
|
||||
|
||||
setTimeout(() => {
|
||||
entry.target.classList.add('animate-visible', 'animate__animated', 'animate__fadeIn')
|
||||
}, delayTime)
|
||||
} else {
|
||||
// 元素离开视口,移除动画类(以便下次进入时重新触发)
|
||||
entry.target.classList.remove('animate-visible', 'animate__animated', 'animate__fadeIn')
|
||||
}
|
||||
})
|
||||
}, { threshold: 0.1 })
|
||||
|
||||
// 观察所有经验项
|
||||
document.querySelectorAll('.timeline-item').forEach((item) => {
|
||||
observer.observe(item)
|
||||
})
|
||||
|
||||
return () => observer.disconnect()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.experience-section {
|
||||
padding: 8rem 0;
|
||||
background-color: #f9fafb;
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.timeline {
|
||||
position: relative;
|
||||
margin-top: 3rem;
|
||||
|
||||
.timeline-axis {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 2px;
|
||||
background-color: #e5e7eb;
|
||||
transform: translateX(-50%);
|
||||
|
||||
@media (max-width: 768px) {
|
||||
left: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin-bottom: 4rem;
|
||||
width: 50%;
|
||||
|
||||
&.timeline-item-left {
|
||||
left: 0;
|
||||
padding-right: 2rem;
|
||||
|
||||
.timeline-content {
|
||||
text-align: right;
|
||||
|
||||
.experience-tags {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-image {
|
||||
display: none;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: -50%;
|
||||
top: 0;
|
||||
width: calc(50% - 2rem);
|
||||
padding-left: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.timeline-item-right {
|
||||
left: 50%;
|
||||
padding-left: 2rem;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
left: 0;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
.timeline-image {
|
||||
display: none;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -50%;
|
||||
top: 0;
|
||||
width: calc(50% - 2rem);
|
||||
padding-right: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
width: 100%;
|
||||
padding-left: 60px;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
position: absolute;
|
||||
right: -10px;
|
||||
top: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background-color: #409EFF;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
box-shadow: 0 0 0 4px rgba(64, 158, 255, 0.1);
|
||||
|
||||
i {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
&.timeline-item-right & {
|
||||
left: -10px;
|
||||
right: auto;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
left: 20px;
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 1.5rem;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
|
||||
.experience-header {
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.period {
|
||||
display: inline-block;
|
||||
font-size: 0.875rem;
|
||||
color: #409EFF;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.position {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.25rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.company {
|
||||
font-size: 1rem;
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.experience-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.experience-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
97
src/components/Footer.vue
Normal file
97
src/components/Footer.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<footer class="footer">
|
||||
<div class="container">
|
||||
<div class="footer-content">
|
||||
<div class="footer-brand">
|
||||
<a href="#" class="logo">
|
||||
Resume<span class="logo-highlight">.</span>
|
||||
</a>
|
||||
<p class="brand-description">专业前端工程师的个人简历</p>
|
||||
</div>
|
||||
|
||||
<div class="footer-copyright">
|
||||
<p>© {{ new Date().getFullYear() }} {{ name }}. 保留所有权利.</p>
|
||||
<p class="footer-credit">设计与开发 <i class="fa fa-heart"></i></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps } from 'vue';
|
||||
|
||||
// 接收姓名属性
|
||||
const props = defineProps({
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.footer {
|
||||
background-color: #1d2129;
|
||||
color: #86909c;
|
||||
padding: 4rem 0 2rem;
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-brand {
|
||||
margin-bottom: 2rem;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
|
||||
.logo-highlight {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
.brand-description {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-copyright {
|
||||
p {
|
||||
margin: 0 0 0.5rem 0;
|
||||
}
|
||||
|
||||
.footer-credit {
|
||||
color: #409EFF;
|
||||
|
||||
i {
|
||||
color: #f56c6c;
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
199
src/components/HeroSection.vue
Normal file
199
src/components/HeroSection.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<section id="about" class="hero-section">
|
||||
<div class="container">
|
||||
<div class="hero-content">
|
||||
<div class="hero-image animate__animated animate__fadeIn">
|
||||
<div class="image-container">
|
||||
<img :src="getAssetsImageFile('avatar.jpg')" alt="个人头像" class="profile-image">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero-info animate__animated animate__fadeIn animate__delay-300">
|
||||
<h1>
|
||||
{{ personalInfo.name }} <span class="title-highlight">/ {{ personalInfo.title }}</span>
|
||||
</h1>
|
||||
<p class="description">{{ personalInfo.description }}</p>
|
||||
|
||||
<!-- 社交媒体链接 -->
|
||||
<div class="social-links">
|
||||
<a
|
||||
v-for="(social, index) in personalInfo.socials"
|
||||
:key="index"
|
||||
:href="social.url"
|
||||
:title="social.name"
|
||||
class="social-link animate__animated animate__bounceIn"
|
||||
:style="{ animationDelay: `${index * 0.1}s` }"
|
||||
>
|
||||
<i :class="social.icon"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- 按钮 -->
|
||||
<div class="action-buttons">
|
||||
<el-button
|
||||
type="primary"
|
||||
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> 联系我
|
||||
</el-button>
|
||||
<el-button
|
||||
size="large"
|
||||
class="ml-4 animate__animated animate__fadeInUp animate__delay-700"
|
||||
>
|
||||
<i class="fa fa-download mr-2"></i> 下载简历
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps } from 'vue'
|
||||
import { getAssetsImageFile } from '@/utils/index.js'
|
||||
|
||||
// 接收个人信息属性
|
||||
const props = defineProps({
|
||||
personalInfo: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
// 定义事件
|
||||
const emits = defineEmits(['scroll-to-section'])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.hero-section {
|
||||
padding: 12rem 0 8rem;
|
||||
background: linear-gradient(135deg, #f5f7fa 0%, #eef1f5 100%);
|
||||
position: relative;
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.hero-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
flex-direction: row;
|
||||
gap: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-image {
|
||||
.image-container {
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
left: -20px;
|
||||
right: -20px;
|
||||
bottom: -20px;
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
border-radius: 50%;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-image {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
border-radius: 50%;
|
||||
object-fit: contain;
|
||||
border: 5px solid white;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.hero-info {
|
||||
text-align: center;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.title-highlight {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1.1rem;
|
||||
color: #666;
|
||||
margin-bottom: 2rem;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.social-links {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.social-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: white;
|
||||
color: #666;
|
||||
margin: 0 0.5rem;
|
||||
text-decoration: none;
|
||||
box-shadow: 0 4px 10px 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
flex-direction: row;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.el-button {
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 6px 15px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
199
src/components/Navbar.vue
Normal file
199
src/components/Navbar.vue
Normal file
@@ -0,0 +1,199 @@
|
||||
<template>
|
||||
<header
|
||||
class="navbar"
|
||||
:class="{ 'navbar-scrolled': scrollPosition > 50 }"
|
||||
>
|
||||
<div class="container">
|
||||
<div class="navbar-content">
|
||||
<a href="#" class="logo">
|
||||
Resume<span class="logo-highlight">.</span>
|
||||
</a>
|
||||
|
||||
<!-- 桌面导航 -->
|
||||
<nav class="desktop-nav">
|
||||
<ul>
|
||||
<li><a href="#about" @click.prevent="$emit('scroll-to-section', 'about')">关于我</a></li>
|
||||
<li><a href="#skills" @click.prevent="$emit('scroll-to-section', 'skills')">技能</a></li>
|
||||
<li><a href="#experience" @click.prevent="$emit('scroll-to-section', 'experience')">工作经验</a></li>
|
||||
<li><a href="#education" @click.prevent="$emit('scroll-to-section', 'education')">教育背景</a></li>
|
||||
<li><a href="#contact" @click.prevent="$emit('scroll-to-section', 'contact')">联系方式</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- 移动端菜单按钮 -->
|
||||
<button
|
||||
class="mobile-menu-btn"
|
||||
@click="mobileMenuOpen = !mobileMenuOpen"
|
||||
aria-label="菜单"
|
||||
>
|
||||
<i class="fa" :class="mobileMenuOpen ? 'fa-times' : 'fa-bars'"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 移动端导航菜单 -->
|
||||
<div
|
||||
class="mobile-nav"
|
||||
:class="{ 'mobile-nav-open': mobileMenuOpen }"
|
||||
>
|
||||
<ul>
|
||||
<li><a href="#about" @click.prevent="handleScroll('about')">关于我</a></li>
|
||||
<li><a href="#skills" @click.prevent="handleScroll('skills')">技能</a></li>
|
||||
<li><a href="#experience" @click.prevent="handleScroll('experience')">工作经验</a></li>
|
||||
<li><a href="#education" @click.prevent="handleScroll('education')">教育背景</a></li>
|
||||
<li><a href="#contact" @click.prevent="handleScroll('contact')">联系方式</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, defineProps } from 'vue'
|
||||
|
||||
// 接收滚动位置属性
|
||||
const props = defineProps({
|
||||
scrollPosition: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
})
|
||||
|
||||
// 移动端菜单状态
|
||||
const mobileMenuOpen = ref(false)
|
||||
|
||||
// 处理滚动并关闭菜单
|
||||
const handleScroll = (sectionId) => {
|
||||
mobileMenuOpen.value = false
|
||||
emits('scroll-to-section', sectionId)
|
||||
}
|
||||
|
||||
// 定义事件
|
||||
const emits = defineEmits(['scroll-to-section'])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.navbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
transition: all 0.3s ease;
|
||||
background-color: rgba(255, 255, 255, 0.95);
|
||||
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 {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.navbar-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
text-decoration: none;
|
||||
|
||||
.logo-highlight {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
.desktop-nav {
|
||||
display: block;
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
margin-left: 2rem;
|
||||
|
||||
a {
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-menu-btn {
|
||||
display: none;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mobile-nav {
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
transition: max-height 0.3s ease;
|
||||
|
||||
&.mobile-nav-open {
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 1rem 0;
|
||||
|
||||
li {
|
||||
margin: 0.5rem 0;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
padding: 0.5rem 0;
|
||||
transition: color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 响应式调整
|
||||
@media (max-width: 768px) {
|
||||
.navbar {
|
||||
.desktop-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-menu-btn {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobile-nav {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
162
src/components/SkillsSection.vue
Normal file
162
src/components/SkillsSection.vue
Normal file
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<section :id="sectionId" class="skills-section">
|
||||
<div class="container">
|
||||
<div class="section-title animate__animated animate__fadeIn">
|
||||
<h2>专业技能</h2>
|
||||
<p>多年的开发经验使我掌握了多种前端技术和工具,能够独立完成从设计到部署的全流程开发工作。</p>
|
||||
</div>
|
||||
|
||||
<div class="skills-grid">
|
||||
<div
|
||||
v-for="(skill, index) in skills"
|
||||
:key="index"
|
||||
class="skill-card animate-item"
|
||||
:data-delay="index * 150"
|
||||
>
|
||||
<div class="skill-icon">
|
||||
<i :class="`fa ${skill.icon}`"></i>
|
||||
</div>
|
||||
<h3 class="skill-title">{{ skill.title }}</h3>
|
||||
<p class="skill-description">{{ skill.description }}</p>
|
||||
<div class="skill-tags">
|
||||
<el-tag
|
||||
v-for="(tag, i) in skill.tags"
|
||||
:key="i"
|
||||
type="info"
|
||||
effect="light"
|
||||
>
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, onMounted } from 'vue'
|
||||
|
||||
// 接收技能数据和sectionId
|
||||
const props = defineProps({
|
||||
skills: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
sectionId: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
// 监听滚动,实现元素进入视口时的动画,离开时移除动画
|
||||
onMounted(() => {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
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) => {
|
||||
observer.observe(card)
|
||||
})
|
||||
|
||||
return () => observer.disconnect()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.skills-section {
|
||||
padding: 8rem 0;
|
||||
background-color: white;
|
||||
/* 增加高度让滚动效果更明显 */
|
||||
min-height: 100vh;
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.skills-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 2rem;
|
||||
|
||||
@media (min-width: 768px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
.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 {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 10px;
|
||||
background-color: rgba(64, 158, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 1.5rem;
|
||||
|
||||
i {
|
||||
font-size: 1.5rem;
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
.skill-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.skill-description {
|
||||
color: #666;
|
||||
margin-bottom: 1.5rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.skill-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
22
src/main.ts
Normal file
22
src/main.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
// 引入element-ui组件
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/theme-chalk/src/index.scss'
|
||||
import 'element-plus/theme-chalk/dark/css-vars.css'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
|
||||
// 引入font-awesome
|
||||
import 'font-awesome/css/font-awesome.min.css'
|
||||
import '@fortawesome/fontawesome-free/css/all.css'
|
||||
|
||||
import 'animate.css'
|
||||
|
||||
// 创建Vue3实例
|
||||
const app = createApp(App)
|
||||
// 使用Element UI Plus
|
||||
app.use(ElementPlus, { locale: zhCn })
|
||||
// 挂载到根组件上
|
||||
app.mount('#app')
|
||||
10
src/styles/element/index.module.scss
Normal file
10
src/styles/element/index.module.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
@use 'element-plus/theme-chalk/src/common/var.scss' with (
|
||||
$colors: (
|
||||
'primary': (
|
||||
'base': #009FA8, // 设置你的主题色
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// 也可以在此引入其他 Element Plus 样式
|
||||
//@use 'element-plus/theme-chalk/src/index.scss' as *;
|
||||
85
src/styles/index.module.scss
Normal file
85
src/styles/index.module.scss
Normal file
@@ -0,0 +1,85 @@
|
||||
body {
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
font-family: CustomFont, sans-serif;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
a:focus,
|
||||
a:active {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
a,
|
||||
a:focus,
|
||||
a:hover {
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
span, input {
|
||||
font-family: 'CustomFont', serif;
|
||||
}
|
||||
|
||||
// 明亮主题变量
|
||||
:root {
|
||||
// 基础颜色
|
||||
--color-bg: #F5F5F5;
|
||||
--color-card-bg: #FFFFFF;
|
||||
--color-record-card-bg: #F5F5DC;
|
||||
--color-border-bg: #FFFFFF;
|
||||
--color-text: #000000;
|
||||
}
|
||||
|
||||
// 暗黑主题变量
|
||||
.dark {
|
||||
--color-bg: #000000;
|
||||
--color-card-bg: #000000;
|
||||
--color-record-card-bg: #4C4D4F;
|
||||
--color-border-bg: #4C4D4F;
|
||||
--color-text: #FFFFFF;
|
||||
}
|
||||
|
||||
::view-transition-old(root),
|
||||
::view-transition-new(root) {
|
||||
animation: none;
|
||||
}
|
||||
.dark::view-transition-old(root) {
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
html {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'CustomFont';
|
||||
src: url('@/assets/font/custom.woff2') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@import "//at.alicdn.com/t/c/font_4793264_6bh098pu52x.css";
|
||||
3
src/utils/index.ts
Normal file
3
src/utils/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const getAssetsImageFile = (url: string) => {
|
||||
return new URL(`/src/assets/${url}`, import.meta.url).href
|
||||
}
|
||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
23
tsconfig.app.json
Normal file
23
tsconfig.app.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true,
|
||||
|
||||
// 设置 @ 路径
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
],
|
||||
"vue3-common/*": ["node_modules/vue3-common/dist/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
}
|
||||
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
25
tsconfig.node.json
Normal file
25
tsconfig.node.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
25
vite.config.ts
Normal file
25
vite.config.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
import path from 'path'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue()
|
||||
],
|
||||
// css: {
|
||||
// preprocessorOptions: {
|
||||
// scss: {
|
||||
// additionalData: '@use "./src/styles/element/index.module.scss" as *;'
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
base: '/resume/',
|
||||
resolve: {
|
||||
// 配置路径别名
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src')
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user