Files
MyfronyProject/src/layouts/MainLayout.vue
qingfeng1121 266310dea3 feat: 实现文章搜索功能并优化留言系统
- 添加文章标题搜索功能,支持通过路由参数搜索
- 重构留言板组件,优化留言嵌套结构和交互
- 新增评论演示页面展示嵌套留言功能
- 调整主布局样式和导航菜单路由
- 修复留言板样式问题和数据字段不一致问题
2025-10-16 16:11:58 +08:00

353 lines
8.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="elrow-top" :class="elrowtop">
<el-row justify="center">
<el-col :span="4" v-if="windowwidth">
<div class="grid-content ep-bg-purple-dark">
<div class="logo-text">清疯不颠</div>
</div>
</el-col>
<el-col :span="14" justify="center">
<div class="grid-content ep-bg-purple-dark">
<el-menu :default-active="activeIndex" class="el-menu-demo" :collapse="false" @select="handleSelect">
<el-menu-item index="/home">
首页
</el-menu-item>
<el-menu-item index="/article-list">
目录
</el-menu-item>
<el-menu-item index="/nonsense">
疯言疯语
</el-menu-item>
<el-menu-item index="/about">
关于
</el-menu-item>
<el-menu-item index="/message">
留言板
</el-menu-item>
</el-menu>
</div>
</el-col>
<el-col :span="2" class="search-container" v-if="windowwidth">
<!-- 搜索功能 -->
<div class="search-wrapper">
<button class="search-icon-btn" @click="toggleSearchBox" :class="{ 'active': isSearchBoxOpen }">
<i class="el-icon-search">1</i>
</button>
<div class="search-box-container" :class="{ 'open': isSearchBoxOpen }">
<el-input
v-model="searchKeyword"
placeholder="搜索文章..."
class="search-input"
@keyup.enter="performSearch"
@blur="closeSearchBoxWithDelay"
/>
</div>
</div>
</el-col>
</el-row>
</div>
<!-- Hero 区域 -->
<div class="hero" :class="{ 'newhero': classhero }" v-if="windowwidth">
<h1 class="typewriter">{{ heroText }}</h1>
</div>
<!-- 主内容区域 -->
<div id="content-section" :class="{ 'visible': isconts }">
<div class="nonsensetitle" v-if="classnonsenset">
<div class="nonsensetitleconst">
<h1>发癫中QAQ</h1>
</div>
</div>
<!-- 左侧模块 -->
<LeftModule class="leftmodluepage" :class="{ 'nonsensetmargintop': classnonsenset }" v-if="windowwidth" />
<!-- 内容模块 -->
<RouterView class="RouterViewpage" :class="{ 'nonsensetmargintop': classnonsenset }" />
</div>
<!-- 分页区域 -->
<div class="Pagination">
<!-- 分页组件可以在这里添加 -->
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import LeftModule from '@/components/LeftModule.vue';
// 路由相关
const router = useRouter();
const route = useRoute();
// 响应式状态
const classhero = ref(false);
const isconts = ref(false);
const isScrollingleftmodlue = ref(false);
const elrowtop = ref('transparent');
const classnonsenset = ref(false);
const windowwidth = ref(true);
const activeIndex = ref('/home');
const localhome= '/home';
// 搜索相关状态
const isSearchBoxOpen = ref(false);
const searchKeyword = ref('');
let searchCloseTimer: number | undefined;
// 打字机效果相关
const fullHeroText = '测试打字机效果';
const heroText = ref('');
let heroIndex = 0;
let heroTimer: number | undefined;
/**
* 打字机效果函数
*/
const startTypewriter = () => {
heroText.value = '';
heroIndex = 0;
if (heroTimer) clearInterval(heroTimer);
heroTimer = window.setInterval(() => {
if (heroIndex < fullHeroText.length) {
heroText.value += fullHeroText[heroIndex];
heroIndex++;
} else {
clearInterval(heroTimer);
}
}, 100);
};
/**
* 菜单选择跳转
*/
const handleSelect = (key: string) => {
router.push({ path: key });
};
/**
* 切换搜索框显示/隐藏
*/
const toggleSearchBox = () => {
isSearchBoxOpen.value = !isSearchBoxOpen.value;
// 如果打开搜索框,清除之前的延时关闭定时器
if (isSearchBoxOpen.value && searchCloseTimer) {
clearTimeout(searchCloseTimer);
}
};
/**
* 关闭搜索框
*/
const closeSearchBox = () => {
isSearchBoxOpen.value = false;
};
/**
* 带延迟关闭搜索框(处理点击搜索按钮后的情况)
*/
const closeSearchBoxWithDelay = () => {
if (searchCloseTimer) {
clearTimeout(searchCloseTimer);
}
searchCloseTimer = window.setTimeout(() => {
isSearchBoxOpen.value = false;
}, 300);
};
/**
* 执行搜索操作
*/
const performSearch = () => {
if (searchKeyword.value.trim()) {
// 这里可以根据实际需求实现搜索逻辑
console.log('搜索关键词:', searchKeyword.value);
router.push({ path: `/home/aericletitle/${searchKeyword.value}`});
}
// 搜索后保持搜索框打开状态
if (searchCloseTimer) {
clearTimeout(searchCloseTimer);
}
};
/**
* 根据路由路径设置页面状态
*/
const updatePageState = (url: string) => {
classhero.value = url !== localhome;
classnonsenset.value = url === '/nonsense';
};
/**
* 设置当前激活的菜单项
*/
const setActiveIndex = (path: string) => {
activeIndex.value = path;
};
/**
* 处理窗口大小变化
*/
const handleResize = () => {
windowwidth.value = window.innerWidth > 768;
// 根据屏幕大小调整内容区可见性
if (route.path === localhome) {
isconts.value = window.innerWidth <= 768 ? true : false;
}
};
/**
* 处理滚动事件
*/
const handleScroll = () => {
// 屏幕小于768时只切换导航栏样式不做内容动画
if (window.innerWidth < 768) {
elrowtop.value = window.scrollY > 100 ? 'solid' : 'transparent';
return;
}
// 导航栏样式切换
if (window.scrollY > 1200) {
elrowtop.value = 'hide';
} else {
elrowtop.value = window.scrollY > 100 ? 'solid' : 'transparent';
}
// 首页内容区滚动动画
if (route.path === localhome) {
isconts.value = window.scrollY > 200;
isScrollingleftmodlue.value = window.scrollY > 600;
}
};
/**
* 监听路由变化
*/
watch(() => route.path, (newPath) => {
updatePageState(newPath);
setActiveIndex(newPath);
// 跳转后回到顶部
window.scrollTo({ top: 0, behavior: 'smooth' });
// 首页内容区滚动动画仅大屏下生效
if (newPath === localhome) {
isconts.value = window.innerWidth <= 768 ? true : false;
// 首页时启动打字机
startTypewriter();
} else {
isconts.value = true;
heroText.value =fullHeroText;
if (heroTimer) clearInterval(heroTimer);
}
}, { immediate: true });
/**
* 生命周期钩子
*/
onMounted(() => {
// 初始化窗口大小
handleResize();
// 添加事件监听器
window.addEventListener('resize', handleResize);
window.addEventListener('scroll', handleScroll);
});
onUnmounted(() => {
// 清理事件监听器
window.removeEventListener('resize', handleResize);
window.removeEventListener('scroll', handleScroll);
// 清理定时器
if (heroTimer) clearInterval(heroTimer);
});
</script>
<style scoped>
/* 搜索框样式 */
.search-wrapper {
position: relative;
display: flex;
align-items: center;
height: 100%;
}
.search-icon-btn {
background: none;
border: none;
color: #fff;
font-size: 18px;
cursor: pointer;
/* padding: 8px; */
border-radius: 4px;
transition: all 0.3s ease;
}
.search-icon-btn:hover,
.search-icon-btn.active {
background-color: rgba(255, 255, 255, 0.1);
}
.search-box-container {
position: absolute;
right: 100%;
top: 50%;
transform: translateY(-50%);
display: flex;
align-items: center;
background-color: #fff;
border-radius: 4px;
/* box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); */
overflow: hidden;
width: 0;
opacity: 0;
transition: all 0.3s ease;
z-index: 1000;
}
.search-box-container.open {
width: 100%;
opacity: 1;
}
.search-input {
border: none;
outline: none;
width: 100%;
height: 36px;
/* padding: 0 8px; */
border: none;
}
.search-btn,
.close-btn {
background: none;
border: none;
padding: 8px;
cursor: pointer;
color: #606266;
transition: color 0.3s ease;
}
.search-btn:hover,
.close-btn:hover {
color: #409eff;
}
/* 防止搜索框在小屏幕上重叠 */
@media screen and (max-width: 1200px) {
.search-box-container.open {
width: 250px;
}
}
</style>