前四个页面
This commit is contained in:
parent
74d03d87e4
commit
08a95dd25c
@ -10,6 +10,7 @@
|
|||||||
},
|
},
|
||||||
"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"
|
"vue-router": "4"
|
||||||
|
|||||||
@ -17,18 +17,39 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { ref, onMounted, onUnmounted } from 'vue';
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const route = useRoute();
|
||||||
const container = ref<HTMLElement | null>(null);
|
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 handleNext = () => {
|
||||||
router.forward();
|
const currentIndex = getCurrentRouteIndex();
|
||||||
|
if (currentIndex < routeOrder.length - 1) {
|
||||||
|
router.push({ name: routeOrder[currentIndex + 1] });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePrev = () => {
|
const handlePrev = () => {
|
||||||
router.back();
|
const currentIndex = getCurrentRouteIndex();
|
||||||
|
if (currentIndex > 0) {
|
||||||
|
router.push({ name: routeOrder[currentIndex - 1] });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 禁用右键菜单
|
// 禁用右键菜单
|
||||||
@ -71,8 +92,8 @@ onMounted(() => {
|
|||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
outline: none; /* 移除焦点轮廓 */
|
outline: none;
|
||||||
cursor: default; /* 使用默认光标 */
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.animated-background {
|
.animated-background {
|
||||||
|
|||||||
@ -1,21 +1,44 @@
|
|||||||
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
|
import IntroductionView from '../views/IntroductionView.vue'
|
||||||
const routes: Array<RouteRecordRaw> = [
|
import ContentsView from '../views/ContentsView.vue'
|
||||||
{
|
import HooksConceptView from '../views/HooksConceptView.vue'
|
||||||
path: '/',
|
import HooksMethodView from '../views/HooksMethodView.vue'
|
||||||
name: 'Introduction',
|
import ComparisonView from '../views/ComparisonView.vue'
|
||||||
component: () => import('../views/IntroductionView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/history',
|
|
||||||
name: 'History',
|
|
||||||
component: () => import('../views/HistoryView.vue')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(import.meta.env.BASE_URL),
|
||||||
routes
|
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
|
export default router
|
||||||
|
|||||||
318
src/views/ComparisonView.vue
Normal file
318
src/views/ComparisonView.vue
Normal 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
229
src/views/ContentsView.vue
Normal 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>
|
||||||
215
src/views/HooksConceptView.vue
Normal file
215
src/views/HooksConceptView.vue
Normal 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>
|
||||||
364
src/views/HooksMethodView.vue
Normal file
364
src/views/HooksMethodView.vue
Normal 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<User>({
|
||||||
|
name: 'John',
|
||||||
|
age: ref(25),
|
||||||
|
preferences: {
|
||||||
|
theme: 'dark'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateUser = (data: Partial<User>) => {
|
||||||
|
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<boolean> = computed(() =>
|
||||||
|
user.age >= 18
|
||||||
|
)
|
||||||
|
|
||||||
|
const fullName: ComputedRef<string> = 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<T> {
|
||||||
|
data: Ref<T | null>
|
||||||
|
loading: Ref<boolean>
|
||||||
|
error: Ref<Error | null>
|
||||||
|
}
|
||||||
|
|
||||||
|
const useDataFetching = <T>(query: Ref<string>): QueryResult<T> => {
|
||||||
|
const data = ref<T | null>(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref<Error | null>(null)
|
||||||
|
|
||||||
|
watch(query, async (newQuery) => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
data.value = await fetchData<T>(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<number>) => {
|
||||||
|
const { user, updateUser } = useUserState()
|
||||||
|
const { isAdult, fullName } = useUserStatus(user)
|
||||||
|
|
||||||
|
const postsQuery = computed(() =>
|
||||||
|
`/api/users/${userId.value}/posts`
|
||||||
|
)
|
||||||
|
const {
|
||||||
|
data: userPosts,
|
||||||
|
loading: postsLoading
|
||||||
|
} = useDataFetching<Post[]>(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>
|
||||||
253
src/views/HooksUsageView.vue
Normal file
253
src/views/HooksUsageView.vue
Normal 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,如 onMounted、onUpdated、onUnmounted 等。</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>
|
||||||
@ -478,12 +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:
|
||||||
dayjs "^1.11.13"
|
dayjs "^1.11.13"
|
||||||
hooks "file:C:/Users/yanlongqi/AppData/Local/Yarn/Cache/v6/npm-hooks-0.0.0-3bade944-d2d2-4129-9022-13077f5d891c-1735875890338/node_modules/hooks"
|
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"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user