Compare commits

..

2 Commits

Author SHA1 Message Date
lqyan
08a95dd25c 前四个页面 2025-01-03 13:29:00 +08:00
lqyan
74d03d87e4 首页 2025-01-03 12:58:19 +08:00
14 changed files with 1906 additions and 7 deletions

View File

@ -10,8 +10,10 @@
}, },
"dependencies": { "dependencies": {
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"highlight.js": "^11.11.1",
"hooks": "file:", "hooks": "file:",
"vue": "^3.5.13" "vue": "^3.5.13",
"vue-router": "4"
}, },
"devDependencies": { "devDependencies": {
"@vitejs/plugin-vue": "^5.2.1", "@vitejs/plugin-vue": "^5.2.1",

View File

@ -12,13 +12,27 @@ const vClick: ObjectDirective = {
}); });
} }
}; };
</script> </script>
<template> <template>
<div v-click="'myClick'">我的是div的内容</div> <div>
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<component :is="Component" />
</transition>
</router-view>
<div v-click="'myClick'">我的是div的内容</div>
</div>
</template> </template>
<style scoped> <style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style> </style>

View File

@ -0,0 +1,179 @@
<template>
<div class="slide-container"
@click.right.prevent="handleNext"
@click.left="handlePrev"
@keyup.space="handleNext"
tabindex="0"
ref="container">
<div class="animated-background">
<div class="gradient-circle circle-1"></div>
<div class="gradient-circle circle-2"></div>
<div class="gradient-circle circle-3"></div>
</div>
<div class="slide-content">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
import { useRouter, useRoute } from 'vue-router';
import { ref, onMounted, onUnmounted } from 'vue';
const router = useRouter();
const route = useRoute();
const container = ref<HTMLElement | null>(null);
//
const routeOrder = [
'introduction',
'contents',
'hooks-concept',
'hooks-method',
'comparison',
'History'
];
const getCurrentRouteIndex = () => {
return routeOrder.indexOf(route.name as string);
};
const handleNext = () => {
const currentIndex = getCurrentRouteIndex();
if (currentIndex < routeOrder.length - 1) {
router.push({ name: routeOrder[currentIndex + 1] });
}
};
const handlePrev = () => {
const currentIndex = getCurrentRouteIndex();
if (currentIndex > 0) {
router.push({ name: routeOrder[currentIndex - 1] });
}
};
//
const handleContextMenu = (e: Event) => {
e.preventDefault();
};
onMounted(() => {
//
container.value?.focus();
//
document.addEventListener('contextmenu', handleContextMenu);
//
const handleKeyUp = (e: KeyboardEvent) => {
if (e.code === 'Space') {
handleNext();
}
};
document.addEventListener('keyup', handleKeyUp);
//
onUnmounted(() => {
document.removeEventListener('contextmenu', handleContextMenu);
document.removeEventListener('keyup', handleKeyUp);
});
});
</script>
<style scoped>
.slide-container {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
position: fixed;
top: 0;
left: 0;
background: #fafafa;
outline: none;
cursor: default;
}
.animated-background {
position: fixed;
width: 100vw;
height: 100vh;
overflow: hidden;
z-index: 0;
}
.gradient-circle {
position: absolute;
border-radius: 50%;
filter: blur(40px);
opacity: 0.6;
}
.circle-1 {
width: 50vw;
height: 50vw;
background: linear-gradient(45deg, rgba(255, 140, 0, 0.3), rgba(255, 200, 0, 0.2));
top: -25vw;
right: -25vw;
animation: float1 15s ease-in-out infinite;
}
.circle-2 {
width: 40vw;
height: 40vw;
background: linear-gradient(45deg, rgba(255, 140, 0, 0.2), rgba(255, 160, 0, 0.1));
bottom: -20vw;
left: -20vw;
animation: float2 18s ease-in-out infinite;
}
.circle-3 {
width: 30vw;
height: 30vw;
background: linear-gradient(45deg, rgba(255, 180, 0, 0.2), rgba(255, 140, 0, 0.1));
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
animation: float3 20s ease-in-out infinite;
}
@keyframes float1 {
0%, 100% {
transform: translate(0, 0) rotate(0deg);
}
50% {
transform: translate(-15%, 15%) rotate(30deg);
}
}
@keyframes float2 {
0%, 100% {
transform: translate(0, 0) rotate(0deg);
}
50% {
transform: translate(15%, -15%) rotate(-30deg);
}
}
@keyframes float3 {
0%, 100% {
transform: translate(-50%, -50%) scale(1) rotate(0deg);
}
50% {
transform: translate(-50%, -50%) scale(1.2) rotate(15deg);
}
}
.slide-content {
flex: 1;
padding: 2rem;
width: 100%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
position: relative;
z-index: 1;
}
</style>

View File

@ -1,5 +1,8 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import './style.css' import './style.css'
import App from './App.vue' import App from './App.vue'
import router from './router'
createApp(App).mount('#app') const app = createApp(App)
app.use(router)
app.mount('#app')

44
src/router/index.ts Normal file
View File

@ -0,0 +1,44 @@
import { createRouter, createWebHistory } from 'vue-router'
import IntroductionView from '../views/IntroductionView.vue'
import ContentsView from '../views/ContentsView.vue'
import HooksConceptView from '../views/HooksConceptView.vue'
import HooksMethodView from '../views/HooksMethodView.vue'
import ComparisonView from '../views/ComparisonView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'introduction',
component: IntroductionView
},
{
path: '/contents',
name: 'contents',
component: ContentsView
},
{
path: '/hooks-concept',
name: 'hooks-concept',
component: HooksConceptView
},
{
path: '/hooks-method',
name: 'hooks-method',
component: HooksMethodView
},
{
path: '/comparison',
name: 'comparison',
component: ComparisonView
},
{
path: '/history',
name: 'History',
component: () => import('../views/HistoryView.vue')
}
]
})
export default router

View File

@ -13,6 +13,18 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
a { a {
font-weight: 500; font-weight: 500;
color: #646cff; color: #646cff;
@ -23,7 +35,6 @@ a:hover {
} }
body { body {
margin: 0;
display: flex; display: flex;
place-items: center; place-items: center;
min-width: 320px; min-width: 320px;
@ -59,6 +70,9 @@ button:focus-visible {
} }
#app { #app {
width: 100%;
height: 100vh;
overflow: hidden;
max-width: 1280px; max-width: 1280px;
margin: 0 auto; margin: 0 auto;
padding: 2rem; padding: 2rem;

View File

@ -0,0 +1,318 @@
<template>
<SlideLayout>
<div class="content">
<h1 class="title animate-slide-down">编程范式对比</h1>
<div class="comparison-container animate-fade-in">
<!-- Hooks 方式 -->
<div class="comparison-side hooks-side">
<div class="side-header">
<div class="header-icon">🎯</div>
<h2>Hooks 方式</h2>
</div>
<div class="comparison-content">
<div class="advantages-section">
<h3><span class="icon"></span>优点</h3>
<ul>
<li>
<div class="point-title">逻辑复用</div>
<div class="point-desc">可以将组件逻辑提取到可重用的函数中避免重复代码</div>
</li>
<li>
<div class="point-title">关注点分离</div>
<div class="point-desc">相关的逻辑可以组合在一起而不是分散在不同的生命周期方法中</div>
</li>
<li>
<div class="point-title">代码组织</div>
<div class="point-desc">更好的代码组织方式便于维护和测试</div>
</li>
<li>
<div class="point-title">TypeScript 支持</div>
<div class="point-desc">更好的类型推导和类型安全</div>
</li>
</ul>
</div>
<div class="disadvantages-section">
<h3><span class="icon"></span>缺点</h3>
<ul>
<li>
<div class="point-title">学习曲线</div>
<div class="point-desc">需要理解函数式编程和组合式API的概念</div>
</li>
<li>
<div class="point-title">调试复杂性</div>
<div class="point-desc">当多个 Hooks 组合使用时调试流程可能变得复杂</div>
</li>
</ul>
</div>
</div>
</div>
<div class="comparison-divider">
<div class="divider-line"></div>
<div class="vs-badge">VS</div>
<div class="divider-line"></div>
</div>
<!-- Options API 方式 -->
<div class="comparison-side options-side">
<div class="side-header">
<div class="header-icon">📝</div>
<h2>Options API 方式</h2>
</div>
<div class="comparison-content">
<div class="advantages-section">
<h3><span class="icon"></span>优点</h3>
<ul>
<li>
<div class="point-title">直观清晰</div>
<div class="point-desc">选项式API结构清晰易于理解</div>
</li>
<li>
<div class="point-title">学习门槛低</div>
<div class="point-desc">传统的面向对象思维更容易上手</div>
</li>
<li>
<div class="point-title">模板清晰</div>
<div class="point-desc">数据和方法的来源一目了然</div>
</li>
</ul>
</div>
<div class="disadvantages-section">
<h3><span class="icon"></span>缺点</h3>
<ul>
<li>
<div class="point-title">逻辑复用困难</div>
<div class="point-desc">需要通过 mixins 或高阶组件实现容易产生命名冲突</div>
</li>
<li>
<div class="point-title">代码组织</div>
<div class="point-desc">相关逻辑分散在不同的选项中难以维护</div>
</li>
<li>
<div class="point-title">类型支持</div>
<div class="point-desc">TypeScript 支持相对较弱</div>
</li>
<li>
<div class="point-title">扩展性</div>
<div class="point-desc">随着组件复杂度增加代码变得难以管理</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</SlideLayout>
</template>
<script setup lang="ts">
import SlideLayout from '../components/SlideLayout.vue';
</script>
<style scoped>
.content {
height: 100%;
display: flex;
flex-direction: column;
padding: 1.5rem 3rem;
gap: 2rem;
}
.title {
color: #ff8c00;
font-size: 2.5rem;
font-weight: bold;
text-align: center;
margin: 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.comparison-container {
display: flex;
align-items: stretch;
gap: 1rem;
height: calc(100% - 5rem);
}
.comparison-side {
flex: 1;
background: rgba(255, 255, 255, 0.8);
border-radius: 16px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
display: flex;
flex-direction: column;
overflow: hidden;
transition: all 0.3s ease;
}
.comparison-side:hover {
transform: translateY(-5px);
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.1);
}
.hooks-side {
background: linear-gradient(135deg, rgba(255, 140, 0, 0.1), rgba(255, 255, 255, 0.8));
}
.options-side {
background: linear-gradient(135deg, rgba(66, 184, 131, 0.1), rgba(255, 255, 255, 0.8));
}
.side-header {
padding: 1.5rem;
display: flex;
align-items: center;
gap: 1rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.header-icon {
font-size: 2rem;
}
.side-header h2 {
margin: 0;
font-size: 1.8rem;
color: #2c3e50;
}
.comparison-content {
flex: 1;
padding: 1.5rem;
display: flex;
flex-direction: column;
gap: 2rem;
overflow-y: auto;
}
.advantages-section h3,
.disadvantages-section h3 {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0 0 1rem 0;
font-size: 1.3rem;
color: #2c3e50;
}
.icon {
font-size: 1.3rem;
}
ul {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: 1rem;
}
li {
background: rgba(255, 255, 255, 0.5);
border-radius: 8px;
padding: 1rem;
transition: all 0.2s ease;
}
.hooks-side li:hover {
background: rgba(255, 140, 0, 0.1);
}
.options-side li:hover {
background: rgba(66, 184, 131, 0.1);
}
.point-title {
font-weight: bold;
color: #2c3e50;
margin-bottom: 0.3rem;
}
.point-desc {
color: #666;
font-size: 0.95rem;
line-height: 1.4;
}
.comparison-divider {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
padding: 0 1rem;
}
.divider-line {
flex: 1;
width: 2px;
background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.1), transparent);
}
.vs-badge {
background: #ff8c00;
color: white;
font-weight: bold;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 1.2rem;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
/* 自定义滚动条 */
.comparison-content::-webkit-scrollbar {
width: 6px;
}
.comparison-content::-webkit-scrollbar-track {
background: transparent;
}
.comparison-content::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.1);
border-radius: 3px;
}
.comparison-content::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.2);
}
/* Animations */
.animate-slide-down {
animation: slideDown 1s ease;
}
.animate-fade-in {
animation: fadeIn 1s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

229
src/views/ContentsView.vue Normal file
View File

@ -0,0 +1,229 @@
<template>
<SlideLayout>
<div class="content">
<div class="contents-container animate-fade-in">
<h1 class="title">目录</h1>
<div class="contents-list">
<router-link to="/history" class="content-item">
<span class="item-number">01</span>
<div class="item-content">
<h2>Hooks 历史</h2>
<p>探索 React Hooks Vue Composition API 的演进历程</p>
</div>
<div class="arrow"></div>
</router-link>
<router-link to="/hooks-concept" class="content-item">
<span class="item-number">02</span>
<div class="item-content">
<h2>核心概念</h2>
<p>掌握 Hooks 的基本概念和工作原理</p>
</div>
<div class="arrow"></div>
</router-link>
<router-link to="/hooks-method" class="content-item">
<span class="item-number">03</span>
<div class="item-content">
<h2>使用方法</h2>
<p>学习 Hooks 的常用方法和最佳实践</p>
</div>
<div class="arrow"></div>
</router-link>
<router-link to="/comparison" class="content-item">
<span class="item-number">04</span>
<div class="item-content">
<h2>编程范式对比</h2>
<p>对比 Hooks 与传统编程方式的异同</p>
</div>
<div class="arrow"></div>
</router-link>
<router-link to="/tool-chain" class="content-item">
<span class="item-number">05</span>
<div class="item-content">
<h2>工具链集成</h2>
<p>探讨 Hooks 与现代前端工具链的协同</p>
</div>
<div class="arrow"></div>
</router-link>
<router-link to="/advanced-design" class="content-item">
<span class="item-number">06</span>
<div class="item-content">
<h2>高阶编程设计</h2>
<p> Hooks 视角理解函数式编程与组合式设计</p>
</div>
<div class="arrow"></div>
</router-link>
<router-link to="/maintainability" class="content-item">
<span class="item-number">07</span>
<div class="item-content">
<h2>代码可维护性</h2>
<p>使用 Hooks 提升代码的可读性和可维护性</p>
</div>
<div class="arrow"></div>
</router-link>
<router-link to="/scalable-arch" class="content-item">
<span class="item-number">08</span>
<div class="item-content">
<h2>可扩展架构</h2>
<p>基于 Hooks 构建灵活可扩展的应用架构</p>
</div>
<div class="arrow"></div>
</router-link>
</div>
</div>
</div>
</SlideLayout>
</template>
<style scoped>
.content {
height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.contents-container {
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
border-radius: 24px;
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);
padding: 3rem;
max-width: 1000px;
width: 100%;
max-height: 90vh;
overflow-y: auto;
}
.contents-container::-webkit-scrollbar {
width: 6px;
}
.contents-container::-webkit-scrollbar-track {
background: transparent;
}
.contents-container::-webkit-scrollbar-thumb {
background-color: rgba(255, 140, 0, 0.2);
border-radius: 3px;
}
.contents-container::-webkit-scrollbar-thumb:hover {
background-color: rgba(255, 140, 0, 0.3);
}
.title {
color: #ff8c00;
font-size: 3rem;
font-weight: bold;
text-align: center;
margin: 0 0 3rem 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.contents-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
gap: 1.5rem;
}
.content-item {
display: flex;
align-items: center;
gap: 1.5rem;
padding: 1.5rem;
background: rgba(255, 255, 255, 0.5);
border-radius: 16px;
text-decoration: none;
transition: all 0.3s ease;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.content-item:hover {
transform: translateX(10px);
background: rgba(255, 140, 0, 0.1);
border-color: rgba(255, 140, 0, 0.2);
}
.item-number {
font-size: 2rem;
font-weight: bold;
color: #ff8c00;
opacity: 0.5;
min-width: 60px;
text-align: center;
}
.item-content {
flex: 1;
}
.item-content h2 {
color: #2c3e50;
font-size: 1.5rem;
margin: 0 0 0.5rem 0;
}
.item-content p {
color: #666;
font-size: 1rem;
margin: 0;
line-height: 1.4;
}
.arrow {
font-size: 2rem;
color: #ff8c00;
opacity: 0;
transform: translateX(-10px);
transition: all 0.3s ease;
}
.content-item:hover .arrow {
opacity: 1;
transform: translateX(0);
}
/* Animations */
.animate-fade-in {
animation: fadeIn 1s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 900px) {
.contents-list {
grid-template-columns: 1fr;
}
.contents-container {
padding: 2rem;
}
.title {
font-size: 2.5rem;
margin-bottom: 2rem;
}
}
</style>
<script setup lang="ts">
import SlideLayout from '../components/SlideLayout.vue';
</script>

138
src/views/HistoryView.vue Normal file
View File

@ -0,0 +1,138 @@
<template>
<SlideLayout>
<div class="content">
<h1 class="title animate-slide-down">Hooks 的历史</h1>
<div class="timeline">
<div class="timeline-item animate-fade-in" v-for="(item, index) in history" :key="index" :style="{ animationDelay: `${index * 0.2}s` }">
<div class="timeline-date">{{ item.date }}</div>
<div class="timeline-content">
<h3>{{ item.title }}</h3>
<p>{{ item.description }}</p>
</div>
</div>
</div>
</div>
</SlideLayout>
</template>
<script setup lang="ts">
import SlideLayout from '../components/SlideLayout.vue';
const history = [
{
date: '2016',
title: 'Mixins 时代',
description: 'Vue2 主要使用 Mixins 来复用组件逻辑,但存在命名冲突和数据来源不清晰的问题。'
},
{
date: '2019',
title: 'React Hooks 发布',
description: 'React 16.8 发布 Hooks展示了一种全新的逻辑复用方式。'
},
{
date: '2020',
title: 'Vue3 Composition API',
description: 'Vue3 正式发布,引入 Composition API提供了类似 Hooks 的能力。'
},
{
date: '现在',
title: 'Hooks 生态繁荣',
description: 'Vue 社区出现了大量优秀的 Hooks 库,极大提升了开发效率。'
}
];
</script>
<style scoped>
.content {
padding: 2rem;
}
.title {
color: #ff8c00;
font-size: 3rem;
text-align: center;
margin-bottom: 3rem;
}
.timeline {
max-width: 800px;
margin: 0 auto;
position: relative;
}
.timeline::before {
content: '';
position: absolute;
left: 50%;
transform: translateX(-50%);
width: 2px;
height: 100%;
background-color: #ff8c00;
}
.timeline-item {
display: flex;
justify-content: space-between;
margin-bottom: 2rem;
opacity: 0;
}
.timeline-item:nth-child(odd) {
flex-direction: row-reverse;
}
.timeline-date {
width: 120px;
padding: 0.5rem;
background-color: #ff8c00;
color: white;
text-align: center;
border-radius: 4px;
position: relative;
z-index: 1;
}
.timeline-content {
width: calc(50% - 80px);
padding: 1rem;
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.timeline-content h3 {
color: #ff8c00;
margin-bottom: 0.5rem;
}
/* Animations */
.animate-slide-down {
animation: slideDown 1s ease;
}
.animate-fade-in {
animation: fadeIn 1s ease both;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -0,0 +1,215 @@
<template>
<SlideLayout>
<div class="content">
<h1 class="title animate-slide-down">Hooks 核心概念</h1>
<div class="concepts animate-fade-in">
<div class="top-row">
<div class="concept-card what-is">
<div class="card-icon">💡</div>
<h2>什么是 Hooks</h2>
<p>Hooks 是一种特殊的函数它允许你在函数组件中"钩入" Vue 的特性它们提供了一种在不编写 class 的情况下使用状态和其他 Vue 特性的方法</p>
</div>
</div>
<div class="concept-grid">
<div class="concept-card">
<div class="card-icon">🎯</div>
<h3>组合式函数</h3>
<p>Hooks 本质上是组合式函数它们以 "use" 开头可以在组件之间共享和重用状态逻辑</p>
<div class="code-snippet">
<code>const useCounter = () => {
const count = ref(0)
return { count }
}</code>
</div>
</div>
<div class="concept-card">
<div class="card-icon">🔄</div>
<h3>响应式系统</h3>
<p>Hooks Vue 的响应式系统深度集成可以创建和管理响应式状态</p>
<div class="code-snippet">
<code>const state = reactive({
count: 0,
double: computed(() => state.count * 2)
})</code>
</div>
</div>
<div class="concept-card">
<div class="card-icon"></div>
<h3>生命周期集成</h3>
<p>Hooks 可以访问组件的生命周期实现副作用的管理和清理</p>
<div class="code-snippet">
<code>onMounted(() => {
//
})
onUnmounted(() => {
//
})</code>
</div>
</div>
<div class="concept-card">
<div class="card-icon">🧩</div>
<h3>组合能力</h3>
<p>多个 Hooks 可以组合使用创建更复杂的功能实现关注点分离</p>
<div class="code-snippet">
<code>const { user } = useUser()
const { posts } = usePosts(user)
const { comments } = useComments(posts)</code>
</div>
</div>
</div>
</div>
</div>
</SlideLayout>
</template>
<script setup lang="ts">
import SlideLayout from '../components/SlideLayout.vue';
</script>
<style scoped>
.content {
height: 100%;
display: flex;
flex-direction: column;
padding: 1.5rem 3rem;
gap: 1.5rem;
}
.title {
color: #ff8c00;
font-size: 2.5rem;
font-weight: bold;
text-align: center;
margin: 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.concepts {
display: flex;
flex-direction: column;
gap: 1.5rem;
height: calc(100% - 4rem);
}
.top-row {
height: 25%;
min-height: 120px;
}
.what-is {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 1.5rem !important;
}
.what-is h2 {
font-size: 1.8rem;
margin: 0.5rem 0;
}
.what-is p {
font-size: 1.1rem;
line-height: 1.5;
margin: 0;
max-width: 80%;
text-align: center;
}
.concept-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
height: 75%;
}
.concept-card {
background: rgba(255, 255, 255, 0.8);
border-radius: 16px;
padding: 1.25rem;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
display: flex;
flex-direction: column;
}
.concept-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.1);
}
.card-icon {
font-size: 1.8rem;
margin-bottom: 0.5rem;
}
.concept-card h3 {
color: #333;
font-size: 1.2rem;
margin: 0 0 0.5rem 0;
}
.concept-card p {
color: #666;
font-size: 0.95rem;
line-height: 1.4;
margin: 0 0 0.5rem 0;
flex-grow: 1;
}
.code-snippet {
background: rgba(0, 0, 0, 0.03);
border-radius: 8px;
padding: 0.75rem;
margin-top: auto;
}
.code-snippet code {
font-family: 'Fira Code', monospace;
font-size: 0.85rem;
color: #333;
white-space: pre;
display: block;
}
/* Animations */
.animate-slide-down {
animation: slideDown 1s ease;
}
.animate-fade-in {
animation: fadeIn 1s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -0,0 +1,364 @@
<template>
<SlideLayout>
<div class="content">
<h1 class="title animate-slide-down">Hooks 使用方法</h1>
<div class="methods-grid animate-fade-in">
<div class="method-card">
<h2>状态管理</h2>
<div class="code-example">
<div class="code-description">
<p>使用 ref reactive 管理响应式状态</p>
</div>
<pre><code class="language-typescript">import { ref, reactive } from 'vue'
interface User {
name: string
age: number
preferences: {
theme: string
}
}
const useUserState = () => {
const user = reactive&lt;User&gt;({
name: 'John',
age: ref(25),
preferences: {
theme: 'dark'
}
})
const updateUser = (data: Partial&lt;User&gt;) => {
Object.assign(user, data)
}
return { user, updateUser }
}</code></pre>
</div>
</div>
<div class="method-card">
<h2>计算属性</h2>
<div class="code-example">
<div class="code-description">
<p>使用 computed 创建派生状态</p>
</div>
<pre><code class="language-typescript">import { computed, ComputedRef } from 'vue'
interface User {
age: number
firstName: string
lastName: string
}
const useUserStatus = (user: User) => {
const isAdult: ComputedRef&lt;boolean&gt; = computed(() =>
user.age >= 18
)
const fullName: ComputedRef&lt;string&gt; = computed(() =>
`${user.firstName} ${user.lastName}`
)
return { isAdult, fullName }
}</code></pre>
</div>
</div>
<div class="method-card">
<h2>副作用处理</h2>
<div class="code-example">
<div class="code-description">
<p>使用 watch 和生命周期钩子管理副作用</p>
</div>
<pre><code class="language-typescript">import { ref, Ref, watch, onMounted } from 'vue'
interface QueryResult&lt;T&gt; {
data: Ref&lt;T | null&gt;
loading: Ref&lt;boolean&gt;
error: Ref&lt;Error | null&gt;
}
const useDataFetching = &lt;T&gt;(query: Ref&lt;string&gt;): QueryResult&lt;T&gt; => {
const data = ref&lt;T | null&gt;(null)
const loading = ref(false)
const error = ref&lt;Error | null&gt;(null)
watch(query, async (newQuery) => {
loading.value = true
try {
data.value = await fetchData&lt;T&gt;(newQuery)
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
})
onMounted(() => {
//
if (query.value) {
watch.value()
}
})
return { data, loading, error }
}</code></pre>
</div>
</div>
<div class="method-card">
<h2>组合使用</h2>
<div class="code-example">
<div class="code-description">
<p>组合多个 Hooks 创建复杂功能</p>
</div>
<pre><code class="language-typescript">interface Post {
id: number
title: string
content: string
}
const useUserDashboard = (userId: Ref&lt;number&gt;) => {
const { user, updateUser } = useUserState()
const { isAdult, fullName } = useUserStatus(user)
const postsQuery = computed(() =>
`/api/users/${userId.value}/posts`
)
const {
data: userPosts,
loading: postsLoading
} = useDataFetching&lt;Post[]&gt;(postsQuery)
return {
user,
updateUser,
isAdult,
fullName,
userPosts,
postsLoading
}
}</code></pre>
</div>
</div>
</div>
</div>
</SlideLayout>
</template>
<script setup lang="ts">
import SlideLayout from '../components/SlideLayout.vue';
import hljs from 'highlight.js/lib/core';
import typescript from 'highlight.js/lib/languages/typescript';
import 'highlight.js/styles/github-dark.css';
import { onMounted } from 'vue';
// typescript
hljs.registerLanguage('typescript', typescript);
onMounted(() => {
document.querySelectorAll('pre code').forEach((block) => {
hljs.highlightElement(block as HTMLElement);
});
});
</script>
<style scoped>
.content {
height: 100%;
display: flex;
flex-direction: column;
padding: 1.5rem 3rem;
gap: 1.5rem;
}
.title {
color: #ff8c00;
font-size: 2.5rem;
font-weight: bold;
text-align: center;
margin: 0;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.methods-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1.5rem;
height: calc(100% - 4rem);
overflow: hidden;
}
.method-card {
background: rgba(255, 255, 255, 0.8);
border-radius: 16px;
padding: 1.25rem;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
display: flex;
flex-direction: column;
overflow: hidden;
}
.method-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.1);
}
.method-card h2 {
color: #333;
font-size: 1.5rem;
margin: 0 0 1rem 0;
position: relative;
display: inline-block;
}
.method-card h2::after {
content: '';
position: absolute;
bottom: -3px;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(to right, #ff8c00, transparent);
border-radius: 1px;
}
.code-example {
display: flex;
flex-direction: column;
gap: 0.75rem;
height: calc(100% - 3rem);
overflow: hidden;
}
.code-description {
color: #666;
font-size: 0.95rem;
line-height: 1.4;
}
.code-description p {
margin: 0;
}
pre {
margin: 0;
flex-grow: 1;
overflow: auto;
background: rgba(255, 140, 0, 0.05) !important;
border-radius: 8px;
border: 1px solid rgba(255, 140, 0, 0.1);
scrollbar-width: thin;
scrollbar-color: rgba(255, 140, 0, 0.3) transparent;
}
pre::-webkit-scrollbar {
width: 8px;
height: 8px;
}
pre::-webkit-scrollbar-track {
background: transparent;
}
pre::-webkit-scrollbar-thumb {
background-color: rgba(255, 140, 0, 0.2);
border-radius: 4px;
}
pre::-webkit-scrollbar-thumb:hover {
background-color: rgba(255, 140, 0, 0.3);
}
code {
font-family: 'Fira Code', monospace;
font-size: 0.85rem;
padding: 1rem !important;
border-radius: 8px;
height: 100%;
text-align: left;
color: #2c3e50 !important;
}
:deep(.hljs) {
background: transparent !important;
color: #2c3e50 !important;
}
:deep(.hljs-keyword) {
color: #ff8c00 !important;
}
:deep(.hljs-string) {
color: #42b883 !important;
}
:deep(.hljs-function) {
color: #2c3e50 !important;
}
:deep(.hljs-title) {
color: #6a51b2 !important;
}
:deep(.hljs-params) {
color: #2c3e50 !important;
}
:deep(.hljs-literal) {
color: #ff8c00 !important;
}
:deep(.hljs-number) {
color: #eb5757 !important;
}
:deep(.hljs-type) {
color: #6a51b2 !important;
}
:deep(.hljs-built_in) {
color: #ff8c00 !important;
}
:deep(.hljs-comment) {
color: #999 !important;
font-style: italic;
}
/* Animations */
.animate-slide-down {
animation: slideDown 1s ease;
}
.animate-fade-in {
animation: fadeIn 1s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -0,0 +1,253 @@
<template>
<SlideLayout>
<div class="content">
<h1 class="title animate-slide-down">Hooks 的使用</h1>
<div class="sections animate-fade-in">
<!-- 概念部分 -->
<section class="concept-section">
<h2>核心概念</h2>
<div class="concept-cards">
<div class="concept-card">
<div class="card-icon">🎯</div>
<h3>什么是 Hooks</h3>
<p>Hooks 是一种函数式编程范式允许我们在函数组件中使用状态和其他 Vue 特性它们以 "use" 开头可以提取和重用组件逻辑</p>
</div>
<div class="concept-card">
<div class="card-icon">🔄</div>
<h3>生命周期</h3>
<p>Hooks 遵循 Vue 组件的生命周期但使用更直观的函数式API onMountedonUpdatedonUnmounted </p>
</div>
<div class="concept-card">
<div class="card-icon">🧩</div>
<h3>组合性</h3>
<p>多个 Hooks 可以组合使用创建更复杂的功能这种组合方式使代码更模块化更易于测试和维护</p>
</div>
</div>
</section>
<!-- 使用方法部分 -->
<section class="usage-section">
<h2>使用方法</h2>
<div class="code-examples">
<div class="code-card">
<h3>基础状态管理</h3>
<pre><code>import { ref, computed } from 'vue'
const useCounter = () => {
const count = ref(0)
const double = computed(() => count.value * 2)
const increment = () => count.value++
const decrement = () => count.value--
return {
count,
double,
increment,
decrement
}
}</code></pre>
</div>
<div class="code-card">
<h3>副作用处理</h3>
<pre><code>import { ref, watch, onMounted, onUnmounted } from 'vue'
const useWindowSize = () => {
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
const handleResize = () => {
width.value = window.innerWidth
height.value = window.innerHeight
}
onMounted(() => {
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
})
return { width, height }
}</code></pre>
</div>
<div class="code-card">
<h3>组合多个 Hooks</h3>
<pre><code>const useUserProfile = () => {
const { width } = useWindowSize()
const { count } = useCounter()
const isMobile = computed(() => width.value < 768)
const userLevel = computed(() =>
count.value > 10 ? 'Advanced' : 'Beginner'
)
return { isMobile, userLevel }
}</code></pre>
</div>
</div>
</section>
</div>
</div>
</SlideLayout>
</template>
<script setup lang="ts">
import SlideLayout from '../components/SlideLayout.vue';
</script>
<style scoped>
.content {
height: 100%;
display: flex;
flex-direction: column;
padding: 2rem 4rem;
gap: 2rem;
overflow-y: auto;
}
.title {
color: #ff8c00;
font-size: 3rem;
font-weight: bold;
margin: 0;
text-align: center;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
}
.sections {
display: flex;
flex-direction: column;
gap: 3rem;
}
section h2 {
color: #333;
font-size: 2rem;
margin-bottom: 1.5rem;
position: relative;
display: inline-block;
}
section h2::after {
content: '';
position: absolute;
bottom: -5px;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(to right, #ff8c00, transparent);
border-radius: 2px;
}
/* 概念卡片样式 */
.concept-cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
margin-bottom: 2rem;
}
.concept-card {
background: rgba(255, 255, 255, 0.8);
padding: 1.5rem;
border-radius: 12px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.concept-card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 12px rgba(255, 140, 0, 0.1);
}
.card-icon {
font-size: 2rem;
margin-bottom: 1rem;
}
.concept-card h3 {
color: #333;
font-size: 1.2rem;
margin-bottom: 0.5rem;
}
.concept-card p {
color: #666;
font-size: 0.95rem;
line-height: 1.5;
}
/* 代码示例样式 */
.code-examples {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.code-card {
background: rgba(255, 255, 255, 0.8);
padding: 1.5rem;
border-radius: 12px;
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.code-card h3 {
color: #333;
font-size: 1.2rem;
margin-bottom: 1rem;
}
.code-card pre {
background: rgba(0, 0, 0, 0.03);
padding: 1rem;
border-radius: 8px;
overflow-x: auto;
}
.code-card code {
font-family: 'Fira Code', monospace;
font-size: 0.9rem;
color: #333;
line-height: 1.5;
}
/* Animations */
.animate-slide-down {
animation: slideDown 1s ease;
}
.animate-fade-in {
animation: fadeIn 1s ease;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -0,0 +1,107 @@
<template>
<SlideLayout>
<div class="content">
<div class="header animate-slide-down">
<h1 class="title">VueHooks</h1>
<div class="subtitle">优雅的代码复用方案</div>
</div>
<div class="presenter-info animate-fade-in">
<div class="info-text">
专业服务开发部 · 燕陇琪 · {{ formatDate() }}
</div>
</div>
</div>
</SlideLayout>
</template>
<script setup lang="ts">
import SlideLayout from '../components/SlideLayout.vue';
const formatDate = () => {
const date = new Date('2025-01-03T12:06:33+08:00');
return `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}`;
};
</script>
<style scoped>
.content {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 3rem;
}
.header {
text-align: center;
}
.title {
color: #ff8c00;
font-size: 8rem;
font-weight: bold;
margin-bottom: 2rem;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.1);
letter-spacing: 4px;
}
.subtitle {
font-size: 2.5rem;
color: #666;
letter-spacing: 2px;
}
.presenter-info {
position: fixed;
bottom: 2rem;
left: 0;
width: 100%;
text-align: center;
padding: 1rem;
}
.info-text {
font-size: 1.2rem;
color: #666;
letter-spacing: 1px;
display: inline-block;
padding: 0.5rem 2rem;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(10px);
border-radius: 50px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
border: 1px solid rgba(255, 255, 255, 0.2);
}
/* Animations */
.animate-slide-down {
animation: slideDown 1s ease;
}
.animate-fade-in {
animation: fadeIn 1s ease 0.5s both;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-50px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>

View File

@ -333,6 +333,11 @@
de-indent "^1.0.2" de-indent "^1.0.2"
he "^1.2.0" he "^1.2.0"
"@vue/devtools-api@^6.6.4":
version "6.6.4"
resolved "https://mirrors.yuchat.top/repository/npmjs/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343"
integrity sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==
"@vue/language-core@2.2.0": "@vue/language-core@2.2.0":
version "2.2.0" version "2.2.0"
resolved "https://mirrors.yuchat.top/repository/npmjs/@vue/language-core/-/language-core-2.2.0.tgz#e48c54584f889f78b120ce10a050dfb316c7fcdf" resolved "https://mirrors.yuchat.top/repository/npmjs/@vue/language-core/-/language-core-2.2.0.tgz#e48c54584f889f78b120ce10a050dfb316c7fcdf"
@ -473,11 +478,18 @@ he@^1.2.0:
resolved "https://mirrors.yuchat.top/repository/npmjs/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" resolved "https://mirrors.yuchat.top/repository/npmjs/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
highlight.js@^11.11.1:
version "11.11.1"
resolved "https://mirrors.yuchat.top/repository/npmjs/highlight.js/-/highlight.js-11.11.1.tgz#fca06fa0e5aeecf6c4d437239135fabc15213585"
integrity sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==
"hooks@file:.": "hooks@file:.":
version "0.0.0" version "0.0.0"
dependencies: dependencies:
hooks "file:C:/Users/Administrator/AppData/Local/Yarn/Cache/v6/npm-hooks-0.0.0-02bdcb68-002e-4d83-9f65-7dd5aa63d929-1735441352086/node_modules/hooks" dayjs "^1.11.13"
hooks "file:C:/Users/yanlongqi/AppData/Local/Yarn/Cache/v6/npm-hooks-0.0.0-3c6b1486-789e-4725-9581-a6da575f3e2f-1735881618100/node_modules/hooks"
vue "^3.5.13" vue "^3.5.13"
vue-router "4"
magic-string@^0.30.11: magic-string@^0.30.11:
version "0.30.17" version "0.30.17"
@ -576,6 +588,13 @@ vscode-uri@^3.0.8:
resolved "https://mirrors.yuchat.top/repository/npmjs/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f" resolved "https://mirrors.yuchat.top/repository/npmjs/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f"
integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw== integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==
vue-router@4:
version "4.5.0"
resolved "https://mirrors.yuchat.top/repository/npmjs/vue-router/-/vue-router-4.5.0.tgz#58fc5fe374e10b6018f910328f756c3dae081f14"
integrity sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==
dependencies:
"@vue/devtools-api" "^6.6.4"
vue-tsc@^2.2.0: vue-tsc@^2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://mirrors.yuchat.top/repository/npmjs/vue-tsc/-/vue-tsc-2.2.0.tgz#dd06c56636f760d7534b7a7a0f6669ba93c217b8" resolved "https://mirrors.yuchat.top/repository/npmjs/vue-tsc/-/vue-tsc-2.2.0.tgz#dd06c56636f760d7534b7a7a0f6669ba93c217b8"