|
|
<template>
|
|
|
<div class="p-6 min-h-full bg-white">
|
|
|
<!-- 顶部区域:扁平化标题与搜索 -->
|
|
|
<div class="flex flex-col md:flex-row md:items-center justify-between mb-8 pb-6 border-b-2 border-gray-100">
|
|
|
<div class="mb-4 md:mb-0">
|
|
|
<h1 class="text-2xl font-black text-gray-900 tracking-tight">JS <span class="text-primary">CORE</span> NAV</h1>
|
|
|
<p class="text-sm text-gray-500 mt-1 font-medium">JavaScript 核心基础知识点速查</p>
|
|
|
</div>
|
|
|
|
|
|
<div class="relative">
|
|
|
<el-input
|
|
|
v-model="searchText"
|
|
|
placeholder="输入关键字过滤..."
|
|
|
class="custom-search !w-full md:!w-80"
|
|
|
clearable
|
|
|
>
|
|
|
<template #prefix>
|
|
|
<iconify-icon-online
|
|
|
icon="ep:search"
|
|
|
class="text-gray-400"
|
|
|
/>
|
|
|
</template>
|
|
|
</el-input>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 导航网格:扁平化高对比度设计 -->
|
|
|
<div
|
|
|
v-if="filteredList.length > 0"
|
|
|
class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"
|
|
|
>
|
|
|
<div
|
|
|
v-for="(item, index) in filteredList"
|
|
|
:key="item.path"
|
|
|
class="nav-card group cursor-pointer relative overflow-hidden border-2 transition-all duration-200"
|
|
|
:style="{ borderColor: getThemeColor(index, 'border') }"
|
|
|
@click="handleNav(item)"
|
|
|
>
|
|
|
<!-- 左侧色条对比 -->
|
|
|
<div
|
|
|
class="absolute left-0 top-0 bottom-0 w-1.5"
|
|
|
:style="{ backgroundColor: getThemeColor(index, 'main') }"
|
|
|
></div>
|
|
|
|
|
|
<div class="p-5 pl-7">
|
|
|
<!-- 标题:高对比度加粗 -->
|
|
|
<h3
|
|
|
class="text-lg font-bold mb-2 transition-colors duration-200"
|
|
|
:style="{ color: getThemeColor(index, 'text') }"
|
|
|
>
|
|
|
{{ item.title }}
|
|
|
</h3>
|
|
|
|
|
|
<!-- 描述:简洁明了 -->
|
|
|
<p class="text-sm text-gray-600 leading-relaxed font-medium line-clamp-2">
|
|
|
{{ item.desc }}
|
|
|
</p>
|
|
|
|
|
|
<!-- 底部装饰线 -->
|
|
|
<div
|
|
|
class="mt-4 w-8 h-1 transition-all duration-300 group-hover:w-full"
|
|
|
:style="{ backgroundColor: getThemeColor(index, 'main') }"
|
|
|
></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 空状态 -->
|
|
|
<el-empty
|
|
|
v-else
|
|
|
description="未找到相关内容"
|
|
|
/>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
import { ref, computed } from 'vue';
|
|
|
import { useRouter } from 'vue-router';
|
|
|
|
|
|
defineOptions({
|
|
|
name: 'JsCoreNav',
|
|
|
});
|
|
|
|
|
|
const router = useRouter();
|
|
|
const searchText = ref('');
|
|
|
|
|
|
interface NavItem {
|
|
|
title: string;
|
|
|
path: string;
|
|
|
desc: string;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 预设高对比度色彩库 (扁平化风格)
|
|
|
*/
|
|
|
const THEME_COLORS = [
|
|
|
{ main: '#3B82F6', border: '#DBEafe', text: '#1E40AF' }, // 蓝色
|
|
|
{ main: '#EF4444', border: '#FEE2E2', text: '#991B1B' }, // 红色
|
|
|
{ main: '#10B981', border: '#D1FAE5', text: '#065F46' }, // 绿色
|
|
|
{ main: '#F59E0B', border: '#FEF3C7', text: '#92400E' }, // 橙色
|
|
|
{ main: '#8B5CF6', border: '#EDE9FE', text: '#5B21B6' }, // 紫色
|
|
|
{ main: '#EC4899', border: '#FCE7F3', text: '#9D174D' }, // 粉色
|
|
|
{ main: '#06B6D4', border: '#CFFAFE', text: '#155E75' }, // 青色
|
|
|
];
|
|
|
|
|
|
const getThemeColor = (index: number, type: 'main' | 'border' | 'text') => {
|
|
|
const colorSet = THEME_COLORS[index % THEME_COLORS.length];
|
|
|
return colorSet[type];
|
|
|
};
|
|
|
|
|
|
/**
|
|
|
* 导航数据
|
|
|
*/
|
|
|
const navList = ref<NavItem[]>([
|
|
|
{
|
|
|
title: '变量提升与作用域',
|
|
|
path: '/js/scope-hoisting',
|
|
|
desc: '执行上下文、词法作用域与 var/let/const 的底层差异。',
|
|
|
},
|
|
|
{
|
|
|
title: '闭包与内存模型',
|
|
|
path: '/js/closures',
|
|
|
desc: '函数作用域链的延续、私有变量实现与内存泄漏规避。',
|
|
|
},
|
|
|
{
|
|
|
title: '原型继承链',
|
|
|
path: '/demo/prototype',
|
|
|
desc: '从 __proto__ 到 prototype,构建 JavaScript 的对象继承大厦。',
|
|
|
},
|
|
|
{
|
|
|
title: 'This 上下文绑定',
|
|
|
path: '/js/this-context',
|
|
|
desc: '默认绑定、隐式绑定、显式绑定与箭头函数的静态指向。',
|
|
|
},
|
|
|
{
|
|
|
title: '事件循环模型',
|
|
|
path: '/demo/jsEventLoop',
|
|
|
desc: '宏任务与微任务的交替执行,浏览器渲染帧的调度机制。',
|
|
|
},
|
|
|
]);
|
|
|
|
|
|
const filteredList = computed(() => {
|
|
|
const keyword = searchText.value.trim().toLowerCase();
|
|
|
if (!keyword) return navList.value;
|
|
|
return navList.value.filter((item) => item.title.toLowerCase().includes(keyword) || item.desc.toLowerCase().includes(keyword));
|
|
|
});
|
|
|
|
|
|
const handleNav = (item: NavItem) => {
|
|
|
if (item.path.startsWith('http')) {
|
|
|
window.open(item.path, '_blank');
|
|
|
} else {
|
|
|
router.push(item.path).catch(() => {});
|
|
|
}
|
|
|
};
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.nav-card {
|
|
|
background-color: #fff;
|
|
|
|
|
|
&:hover {
|
|
|
transform: translateX(4px);
|
|
|
background-color: #f9fafb;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.custom-search {
|
|
|
:deep(.el-input__wrapper) {
|
|
|
box-shadow: none !important;
|
|
|
border: 2px solid #f3f4f6;
|
|
|
border-radius: 0;
|
|
|
padding: 8px 12px;
|
|
|
|
|
|
&.is-focus {
|
|
|
border-color: var(--el-color-primary);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.text-primary {
|
|
|
color: var(--el-color-primary);
|
|
|
}
|
|
|
</style>
|