feat:更新项目经历
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --host 0.0.0.0",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
|
|||||||
793
pnpm-lock.yaml
generated
793
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
BIN
src/assets/IOT消息通讯框架.png
Normal file
BIN
src/assets/IOT消息通讯框架.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
BIN
src/assets/健康安全监护大屏.png
Normal file
BIN
src/assets/健康安全监护大屏.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@@ -19,26 +19,13 @@
|
|||||||
import EducationCard from '@/components/Education/EducationCard.vue'
|
import EducationCard from '@/components/Education/EducationCard.vue'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { useProfileStore } from '@/store/profile.ts'
|
import { useProfileStore } from '@/store/profile.ts'
|
||||||
|
import { getObserver } from '@/utils/index.js'
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
|
|
||||||
// 监听滚动,实现元素进入视口时的动画
|
// 监听滚动,实现元素进入视口时的动画
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const observer = new IntersectionObserver((entries) => {
|
const observer = getObserver()
|
||||||
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) => {
|
document.querySelectorAll('.education-card').forEach((card) => {
|
||||||
|
|||||||
@@ -29,7 +29,8 @@
|
|||||||
|
|
||||||
<!-- 图片 -->
|
<!-- 图片 -->
|
||||||
<div class="timeline-image">
|
<div class="timeline-image">
|
||||||
<img :src="item.image" :alt="item.company + '项目图片'" class="experience-image">
|
<img :src="getAssetsImageFile(item.image)" :alt="item.company + '项目图片'"
|
||||||
|
class="experience-image">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -37,6 +38,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, PropType } from 'vue'
|
import { defineProps, PropType } from 'vue'
|
||||||
import { IExperience } from '@/types'
|
import { IExperience } from '@/types'
|
||||||
|
import { getAssetsImageFile } from '@/utils'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: {
|
item: {
|
||||||
|
|||||||
@@ -24,26 +24,13 @@
|
|||||||
import ExperienceCard from '@/components/Experience/ExperienceCard.vue'
|
import ExperienceCard from '@/components/Experience/ExperienceCard.vue'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { useProfileStore } from '@/store/profile.ts'
|
import { useProfileStore } from '@/store/profile.ts'
|
||||||
|
import { getObserver } from '@/utils/index.js'
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
|
|
||||||
// 监听滚动,实现元素进入视口时的动画
|
// 监听滚动,实现元素进入视口时的动画
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const observer = new IntersectionObserver((entries) => {
|
const observer = getObserver()
|
||||||
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) => {
|
document.querySelectorAll('.timeline-item').forEach((item) => {
|
||||||
|
|||||||
@@ -8,34 +8,34 @@
|
|||||||
<!-- 内容容器 -->
|
<!-- 内容容器 -->
|
||||||
<div class="timeline-content">
|
<div class="timeline-content">
|
||||||
<div class="project-header">
|
<div class="project-header">
|
||||||
<span class="period">{{ item.period }}</span>
|
<span class="period">{{ props.item.period }}</span>
|
||||||
<h3 class="name">{{ item.name }}</h3>
|
<h3 class="name">{{ props.item.name }}</h3>
|
||||||
<h4 class="role">{{ item.role }}</h4>
|
<h4 class="role">{{ props.item.role }}</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="description">{{ item.description }}</p>
|
<p class="description">{{ props.item.description }}</p>
|
||||||
|
|
||||||
<div class="responsibilities" v-if="item.responsibilities && item.responsibilities.length > 0">
|
<div class="content" v-if="props.item.content && props.item.content.length > 0">
|
||||||
<h4>主要职责</h4>
|
<h4>主要职责</h4>
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(resp, i) in item.responsibilities" :key="i">{{ resp }}</li>
|
<li v-for="(resp, i) in props.item.content" :key="i">{{ resp }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="project-tags">
|
<div class="project-tags">
|
||||||
<el-tag
|
<el-tag v-for="(tag, index) in props.item.tags" :key="index" effect="light">
|
||||||
v-for="(tag, index) in item.tags"
|
|
||||||
:key="index"
|
|
||||||
effect="light"
|
|
||||||
>
|
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 图片 -->
|
<!-- 图片 -->
|
||||||
<div class="timeline-image" v-if="item.image">
|
<div class="timeline-image" v-if="props.item.images.length > 0">
|
||||||
<img :src="item.image" :alt="item.name + '项目图片'" class="project-image">
|
<el-carousel height="450px">
|
||||||
|
<el-carousel-item v-for="(item, index) in props.item.images" :key="index">
|
||||||
|
<el-image :src="getAssetsImageFile(item)" fit="contain" class="project-image" />
|
||||||
|
</el-carousel-item>
|
||||||
|
</el-carousel>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineProps, PropType } from 'vue'
|
import { defineProps, PropType } from 'vue'
|
||||||
import { IProject } from '@/types'
|
import { IProject } from '@/types'
|
||||||
|
import { getAssetsImageFile } from '@/utils'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: {
|
item: {
|
||||||
|
|||||||
@@ -24,26 +24,13 @@
|
|||||||
import ProjectCard from '@/components/Project/ProjectCard.vue'
|
import ProjectCard from '@/components/Project/ProjectCard.vue'
|
||||||
import { onMounted } from 'vue'
|
import { onMounted } from 'vue'
|
||||||
import { useProfileStore } from '@/store/profile.ts'
|
import { useProfileStore } from '@/store/profile.ts'
|
||||||
|
import { getObserver } from '@/utils/index.js'
|
||||||
|
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
|
|
||||||
// 监听滚动,实现元素进入视口时的动画
|
// 监听滚动,实现元素进入视口时的动画
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const observer = new IntersectionObserver((entries) => {
|
const observer = getObserver()
|
||||||
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) => {
|
document.querySelectorAll('.timeline-item').forEach((item) => {
|
||||||
@@ -133,9 +120,10 @@ onMounted(() => {
|
|||||||
@media (min-width: 768px) {
|
@media (min-width: 768px) {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: -50%;
|
left: -100%;
|
||||||
top: 0;
|
top: 20%;
|
||||||
width: calc(50% - 2rem);
|
// width: calc(50% - 2rem);
|
||||||
|
width: 100%;
|
||||||
padding-right: 2rem;
|
padding-right: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -205,7 +193,7 @@ onMounted(() => {
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.responsibilities {
|
.content {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ onMounted(() => {
|
|||||||
}, delayTime)
|
}, delayTime)
|
||||||
} else {
|
} else {
|
||||||
// 元素离开视口,移除动画类(以便下次进入时重新触发)
|
// 元素离开视口,移除动画类(以便下次进入时重新触发)
|
||||||
entry.target.classList.remove('animate-visible', 'animate__animated', 'animate__fadeInUp')
|
// entry.target.classList.remove('animate-visible', 'animate__animated', 'animate__fadeInUp')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, { threshold: 0.1 })
|
}, { threshold: 0.1 })
|
||||||
|
|||||||
@@ -66,30 +66,22 @@ export const useProfileStore = defineStore('profile', {
|
|||||||
company: '互联网科技有限公司',
|
company: '互联网科技有限公司',
|
||||||
description: '参与多个企业级Web应用的开发,负责前端页面实现和交互逻辑编写。与产品和设计团队紧密合作,将设计稿转化为高质量的代码,解决各种浏览器兼容性问题。',
|
description: '参与多个企业级Web应用的开发,负责前端页面实现和交互逻辑编写。与产品和设计团队紧密合作,将设计稿转化为高质量的代码,解决各种浏览器兼容性问题。',
|
||||||
tags: ['Vue', 'SCSS', 'Webpack'],
|
tags: ['Vue', 'SCSS', 'Webpack'],
|
||||||
image: 'https://picsum.photos/id/180/400/200'
|
image: ''
|
||||||
},
|
|
||||||
{
|
|
||||||
period: '2016年6月 - 2017年7月',
|
|
||||||
position: '前端开发实习生',
|
|
||||||
company: '数字技术有限公司',
|
|
||||||
description: '参与公司官网和内部管理系统的开发与维护,学习前端开发技术和最佳实践。协助 senior 工程师完成功能模块开发,修复已知bug,优化页面性能。',
|
|
||||||
tags: ['HTML5', 'CSS3', 'jQuery'],
|
|
||||||
image: 'https://picsum.photos/id/48/400/200'
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
education: [
|
education: [
|
||||||
{
|
{
|
||||||
period: '2012年9月 - 2016年6月',
|
period: '2015年9月 - 2017年6月',
|
||||||
degree: '计算机科学与技术',
|
degree: '自动化',
|
||||||
school: '北京大学',
|
school: '南京工业大学',
|
||||||
description: '本科期间主修计算机科学与技术专业,系统学习了数据结构、算法、计算机网络等基础知识,参与多个课程设计和科研项目。',
|
description: '本科期间主修计算机科学与技术专业,系统学习了数据结构、算法、计算机网络等基础知识,参与多个课程设计和科研项目。',
|
||||||
detailsTitle: '主要课程',
|
detailsTitle: '主要课程',
|
||||||
details: ['数据结构', '算法分析', '计算机网络', '操作系统', '数据库原理']
|
details: ['数据结构', '算法分析', '计算机网络', '操作系统', '数据库原理']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
period: '2016年9月 - 2018年6月',
|
period: '2017年9月 - 2020年6月',
|
||||||
degree: '软件工程',
|
degree: '控制科学与工程',
|
||||||
school: '清华大学',
|
school: '南京工业大学',
|
||||||
description: '研究生阶段专注于软件工程领域,研究方向为Web前端技术和用户界面设计,发表2篇相关领域的学术论文,参与校企合作项目开发。',
|
description: '研究生阶段专注于软件工程领域,研究方向为Web前端技术和用户界面设计,发表2篇相关领域的学术论文,参与校企合作项目开发。',
|
||||||
detailsTitle: '主要成果',
|
detailsTitle: '主要成果',
|
||||||
details: ['学术论文2篇', '优秀毕业生', '校企合作项目', '奖学金']
|
details: ['学术论文2篇', '优秀毕业生', '校企合作项目', '奖学金']
|
||||||
@@ -97,32 +89,35 @@ export const useProfileStore = defineStore('profile', {
|
|||||||
],
|
],
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: '企业级电商平台',
|
name: 'Web边缘管理系统',
|
||||||
period: '2022.03 - 2023.06',
|
period: '2024.01 - 2025.11',
|
||||||
role: '前端负责人',
|
role: '全栈开发工程师',
|
||||||
description: '一个完整的B2B电商平台,包含商品管理、订单系统、支付系统等功能模块。',
|
description: '一个面向医疗设备领域的SaaS平台,核心功能是实现对患者信息、康复报告、治疗排班的集中化管理。',
|
||||||
responsibilities: [
|
content: [
|
||||||
'负责前端架构设计和核心模块开发',
|
'前端基于 Vue 3 与 TypeScript 进行开发,采用 Element Plus 组件库构建整体用户界面,并使用 ECharts 实现治疗方案、设备状态等数据的可视化图表与大屏展示。',
|
||||||
'带领5人前端团队完成项目开发',
|
'后端使用 Spring Boot 2 框架构建 RESTful API,处理核心业务逻辑,集成 MyBatis-Plus 作为数据持久层框架。',
|
||||||
'优化前端性能,提升页面加载速度40%',
|
'数据库采用 MySQL,负责数据库表结构设计、索引优化与核心 SQL 编写,确保患者信息、治疗报告、排班等数据的高效存储与查询。',
|
||||||
'实现响应式设计,适配多种设备'
|
'主导开发了患者信息管理中心、多治疗师协同任务管理、康复报告生成与审批、智能治疗排班四大核心功能模块的前端界面与后端接口。',
|
||||||
|
'实现基于 RBAC 模型的权限控制系统,前端进行按钮级权限控制,后端完成接口级访问鉴权。',
|
||||||
|
'使用 Docker 将前后端应用分别容器化,通过 Docker Compose 定义多服务编排,完成项目的一键部署与运维。'
|
||||||
],
|
],
|
||||||
tags: ['前端', 'Vue3', 'TypeScript', '负责人', '电商'],
|
tags: ['SpringBoot2', 'Vue3', 'TypeScript', 'ECharts', '电商'],
|
||||||
image: '/images/projects/ecommerce.jpg'
|
images: []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '智能数据分析系统',
|
name: '健康安全监护物联网平台',
|
||||||
period: '2021.07 - 2022.02',
|
period: '2025.07 - 2025.09',
|
||||||
role: '全栈开发工程师',
|
role: '后端开发工程师 / IoT架构核心成员',
|
||||||
description: '为企业提供数据分析和可视化展示的系统,支持大数据处理和实时分析。',
|
description: '一个基于云-边协同架构的医疗级实时监护平台。项目整合智能手表、毫米波雷达、摄像头等设备,通过MQTT、TCP、HTTP等多协议接入,实现对生命体征与安全行为的实时采集、传输、存储与Web/移动端可视化告警。',
|
||||||
responsibilities: [
|
content: [
|
||||||
'参与前后端功能开发',
|
'主导云端通信枢纽架构,使用Python构建高并发TCP服务器与HTTP服务器,可靠接收智能手表、呼叫器等设备的生命体征与告警数据,并统一转发至MQTT消息队列',
|
||||||
'设计并实现数据可视化模块',
|
'负责边缘服务器核心消息处理模块(.NET)开发,实现MQTT客户端订阅、多源数据(心率、血压、异常事件)聚合,并通过WebSocket服务器向Web/Flutter前端进行毫秒级实时推送',
|
||||||
'优化数据处理算法,提升性能30%',
|
'设计并实现时序数据存储方案,集成InfluxDB持久化存储海量传感器数据(如心率曲线),为前端历史查询与分析提供支持;使用MySQL管理设备、用户等业务数据',
|
||||||
'编写技术文档和单元测试'
|
'集成RTSP to HLS流媒体服务,将视觉相机视频流转换为m3u8格式,供Web前端实现低延迟直播监控,完成“视频+数据”一体化监护',
|
||||||
|
'为Web前端与Flutter移动端提供清晰的HTTP RESTful API,用于设备管理、指令下发与数据查询,确保前后端高效协同'
|
||||||
],
|
],
|
||||||
tags: ['全栈', 'React', 'Node.js', '大数据', '可视化'],
|
tags: ['物联网', 'Python', '.NET', 'MQTT', 'WebSocket', 'TCP/HTTP', 'InfluxDB', 'MySQL', '云边协同', '实时数据处理'],
|
||||||
image: '/images/projects/analytics.jpg'
|
images: ['IOT消息通讯框架.png', '健康安全监护大屏.png']
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
contactInfo: [
|
contactInfo: [
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export interface IProject {
|
|||||||
period: string
|
period: string
|
||||||
role: string
|
role: string
|
||||||
description: string
|
description: string
|
||||||
responsibilities?: string[]
|
content: string[]
|
||||||
tags: string[]
|
tags: string[]
|
||||||
image?: string
|
images: string[]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,22 @@
|
|||||||
export const getAssetsImageFile = (url: string) => {
|
export const getAssetsImageFile = (url: string) => {
|
||||||
return new URL(`/src/assets/${url}`, import.meta.url).href
|
return new URL(`/src/assets/${url}`, import.meta.url).href
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getObserver = () => {
|
||||||
|
return new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
const classList = ['animate-visible', 'animate__animated', 'animate__fadeInUp']
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
const delay = entry.target.getAttribute('data-delay')
|
||||||
|
const delayTime = delay ? parseInt(delay, 10) : 0
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
entry.target.classList.add(...classList)
|
||||||
|
}, delayTime)
|
||||||
|
} else {
|
||||||
|
// 元素离开视口,移除动画类(以便下次进入时重新触发)
|
||||||
|
// entry.target.classList.remove(...classList)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, { threshold: 0.1 })
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user