h5样式
This commit is contained in:
593
schoolNewsWeb/src/layouts/MobileLayout.vue
Normal file
593
schoolNewsWeb/src/layouts/MobileLayout.vue
Normal file
@@ -0,0 +1,593 @@
|
||||
<template>
|
||||
<div class="mobile-layout">
|
||||
<!-- 顶部导航栏 -->
|
||||
<header class="mobile-header">
|
||||
<!-- 左侧:系统标签 -->
|
||||
<div class="header-left">
|
||||
<div class="logo-section">
|
||||
<div class="logo-icon">
|
||||
<img src="@/assets/imgs/logo-icon.svg" alt="Logo" />
|
||||
</div>
|
||||
<h1 class="platform-title">红色思政学习平台</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:搜索、菜单、用户头像 -->
|
||||
<div class="header-right">
|
||||
<!-- 搜索区域 -->
|
||||
<div class="search-section">
|
||||
<!-- 搜索input(展开时显示) -->
|
||||
<div v-if="searchInputVisible" class="search-input-wrapper">
|
||||
<el-input
|
||||
v-model="searchKeyword"
|
||||
ref="searchInputRef"
|
||||
placeholder="搜索内容"
|
||||
class="search-input"
|
||||
@keydown.enter="performSearch"
|
||||
@blur="hideSearchInput"
|
||||
/>
|
||||
</div>
|
||||
<!-- 搜索按钮 -->
|
||||
<button v-if="!searchInputVisible" @click="showSearchInput" class="search-button">
|
||||
<el-icon class="search-icon">
|
||||
<Search />
|
||||
</el-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 汉堡菜单 -->
|
||||
<button @click="toggleNavMenu" class="hamburger-button">
|
||||
<div class="hamburger-lines">
|
||||
<span class="line"></span>
|
||||
<span class="line"></span>
|
||||
<span class="line"></span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<!-- 用户头像 -->
|
||||
<div class="user-avatar-section" @click="handleUserClick">
|
||||
<el-avatar :size="32" :src="userInfo.avatar" class="user-avatar">
|
||||
<el-icon><User /></el-icon>
|
||||
</el-avatar>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 导航下拉菜单 - 使用 Teleport 避免层级问题 -->
|
||||
<Teleport to="body">
|
||||
<div
|
||||
v-show="navMenuVisible"
|
||||
class="nav-dropdown"
|
||||
:class="{ show: navMenuVisible }"
|
||||
@click="handleBackgroundClick"
|
||||
>
|
||||
<div class="nav-menu" @click.stop>
|
||||
<MobileNavItem
|
||||
v-for="menu in navigationMenus"
|
||||
:key="menu.menuID"
|
||||
:menu="menu"
|
||||
@menu-click="handleNavMenuClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
|
||||
<!-- 主要内容区域 -->
|
||||
<main class="mobile-main" :class="{ 'nav-open': navMenuVisible }">
|
||||
<AIAgentMobile/>
|
||||
<router-view />
|
||||
</main>
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, nextTick } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useStore } from 'vuex';
|
||||
import type { SysMenu } from '@/types';
|
||||
import type { Tag } from '@/types/resource';
|
||||
import { MenuType } from '@/types/enums';
|
||||
import { resourceTagApi } from '@/apis/resource';
|
||||
import {
|
||||
ArrowLeft,
|
||||
Menu,
|
||||
Search,
|
||||
More,
|
||||
ArrowRight,
|
||||
SwitchButton,
|
||||
House,
|
||||
Document,
|
||||
User,
|
||||
Message,
|
||||
Setting
|
||||
} from '@element-plus/icons-vue';
|
||||
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||
import { MobileNavItem } from '@/components/base';
|
||||
import { AIAgentMobile } from '@/views/public/ai';
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const store = useStore();
|
||||
|
||||
// 响应式数据
|
||||
const navMenuVisible = ref(false);
|
||||
const searchInputVisible = ref(false);
|
||||
const searchKeyword = ref('');
|
||||
const searchInputRef = ref();
|
||||
const specialTags = ref<Tag[]>([]); // 特殊标签:专题报告和思政案例
|
||||
|
||||
// 计算属性
|
||||
const currentPath = computed(() => route.path);
|
||||
const pageTitle = computed(() => route.meta?.title as string || '校园新闻');
|
||||
const showBackButton = computed(() => route.meta?.showBackButton as boolean || false);
|
||||
const showSearch = computed(() => route.meta?.showSearch as boolean || true); // 默认显示搜索按钮
|
||||
const showMore = computed(() => route.meta?.showMore as boolean || false);
|
||||
const showTabBar = computed(() => route.meta?.showTabBar !== false);
|
||||
|
||||
// 从store获取用户信息
|
||||
const userInfo = computed(() => {
|
||||
const userDetail = store.getters['auth/userinfo'];
|
||||
const user = store.getters['auth/user'];
|
||||
return {
|
||||
avatar: FILE_DOWNLOAD_URL + userDetail?.avatar, // 默认头像
|
||||
name: user?.userName || user?.name || '用户',
|
||||
role: userDetail?.title || '用户'
|
||||
};
|
||||
});
|
||||
|
||||
// 获取所有菜单
|
||||
const allMenus = computed(() => store.getters['auth/menuTree']);
|
||||
|
||||
// 获取导航菜单(过滤掉用户相关菜单)+ 特殊标签
|
||||
const navigationMenus = computed(() => {
|
||||
const menus = allMenus.value.filter((menu: SysMenu) => {
|
||||
// 过滤掉"用户下拉菜单"容器,这些显示在UserDropdown中
|
||||
if (menu.menuID === 'menu_user_dropdown') {
|
||||
return false;
|
||||
}
|
||||
return menu.type === MenuType.NAVIGATION;
|
||||
});
|
||||
|
||||
// 将特殊标签转换为SysMenu格式并添加到菜单中
|
||||
const specialMenus: SysMenu[] = specialTags.value.map(tag => ({
|
||||
menuID: `special_${tag.tagID}`,
|
||||
name: tag.name,
|
||||
url: `/resource-center?tagID=${tag.tagID}`,
|
||||
type: MenuType.NAVIGATION,
|
||||
children: [],
|
||||
parentID: undefined,
|
||||
sort: 999 // 排在最后
|
||||
}));
|
||||
|
||||
return [...menus, ...specialMenus];
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
// 方法
|
||||
const toggleNavMenu = () => {
|
||||
navMenuVisible.value = !navMenuVisible.value;
|
||||
};
|
||||
|
||||
const hideNavMenu = () => {
|
||||
navMenuVisible.value = false;
|
||||
};
|
||||
|
||||
// 处理背景点击 - 参考TopNavigation.vue的处理方式
|
||||
const handleBackgroundClick = (event: MouseEvent) => {
|
||||
// 只有点击背景区域时才关闭菜单
|
||||
if (event.target === event.currentTarget) {
|
||||
hideNavMenu();
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
router.back();
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
router.push('/search');
|
||||
};
|
||||
|
||||
|
||||
// 搜索功能相关方法
|
||||
const showSearchInput = () => {
|
||||
searchInputVisible.value = true;
|
||||
// 下一个tick后聚焦输入框
|
||||
nextTick(() => {
|
||||
searchInputRef.value?.focus?.();
|
||||
});
|
||||
};
|
||||
|
||||
const hideSearchInput = () => {
|
||||
// 延迟隐藏,避免点击搜索按钮时立即隐藏
|
||||
setTimeout(() => {
|
||||
searchInputVisible.value = false;
|
||||
searchKeyword.value = '';
|
||||
}, 200);
|
||||
};
|
||||
|
||||
const performSearch = () => {
|
||||
if (searchKeyword.value.trim()) {
|
||||
// 执行搜索跳转
|
||||
router.push({
|
||||
path: '/search',
|
||||
query: {
|
||||
keyword: searchKeyword.value.trim()
|
||||
}
|
||||
});
|
||||
// 搜索后隐藏输入框
|
||||
searchInputVisible.value = false;
|
||||
searchKeyword.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
// 用户头像点击处理
|
||||
const handleUserClick = () => {
|
||||
router.push('/user-center');
|
||||
};
|
||||
|
||||
// 处理导航菜单点击
|
||||
const handleNavMenuClick = (menu: SysMenu) => {
|
||||
hideNavMenu();
|
||||
if (menu.url) {
|
||||
router.push(menu.url);
|
||||
} else if (menu.children && menu.children.length > 0) {
|
||||
// 如果没有url但有子菜单,跳转到第一个子菜单
|
||||
const firstChild = menu.children.find(child => child.url);
|
||||
if (firstChild?.url) {
|
||||
router.push(firstChild.url);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// 加载特殊标签(专题报告和思政案例)
|
||||
const loadSpecialTags = async () => {
|
||||
try {
|
||||
const res = await resourceTagApi.getTagsByType(1); // 1 = 文章分类标签
|
||||
if (res.success && res.dataList) {
|
||||
// 只获取专题报告(tag005)和思政案例(tag006)
|
||||
specialTags.value = res.dataList.filter((tag: Tag) =>
|
||||
tag.tagID === 'tag_article_005' || tag.tagID === 'tag_article_006'
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载特殊标签失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(async () => {
|
||||
// 加载特殊标签
|
||||
await loadSpecialTags();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.mobile-layout {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.mobile-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 46px;
|
||||
padding: 10px 16px;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid rgba(72, 72, 72, 0.1);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
|
||||
.header-left {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.logo-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7.42px;
|
||||
|
||||
.logo-icon {
|
||||
width: 24.28px;
|
||||
height: 24.28px;
|
||||
background: #C62828;
|
||||
border-radius: 4.05px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 2.02px;
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
width: 20.24px;
|
||||
height: 20.24px;
|
||||
}
|
||||
}
|
||||
|
||||
.platform-title {
|
||||
font-family: 'Taipei Sans TC Beta', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: #C62828;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-shrink: 0;
|
||||
|
||||
.search-section {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.search-input-wrapper {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 200px;
|
||||
z-index: 10;
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
background: #ffffff;
|
||||
border: 1px solid #C62828;
|
||||
border-radius: 20px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
box-shadow: 0 2px 8px rgba(198, 40, 40, 0.1);
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
|
||||
&::placeholder {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
font-size: 18px;
|
||||
color: #141F38;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hamburger-button {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.hamburger-lines {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
.line {
|
||||
width: 13.33px;
|
||||
height: 0;
|
||||
border-top: 1.67px solid #141F38;
|
||||
margin-left: 1.33px;
|
||||
|
||||
&:nth-child(1) {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.user-avatar-section {
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
border: 1px solid #e5e5e5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导航下拉菜单样式
|
||||
.nav-dropdown {
|
||||
position: fixed;
|
||||
top: 46px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
z-index: 10000;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.2s, visibility 0.2s;
|
||||
pointer-events: none;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
background: #ffffff;
|
||||
border-radius: 0 0 20px 20px;
|
||||
padding: 10px 20px;
|
||||
position: relative;
|
||||
|
||||
.nav-menu-item {
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
&.active {
|
||||
.nav-item-label {
|
||||
color: #C62828;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-item-arrow {
|
||||
color: #C62828;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-item-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 2px;
|
||||
|
||||
.nav-item-label {
|
||||
font-family: 'PingFang SC', sans-serif;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #141F38;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.nav-item-arrow {
|
||||
font-size: 12px;
|
||||
color: #686868;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
}
|
||||
|
||||
// 特殊样式处理不同颜色的文本
|
||||
&:nth-child(1) .nav-item-label,
|
||||
&:nth-child(2) .nav-item-label,
|
||||
&:nth-child(6) .nav-item-label,
|
||||
&:nth-child(7) .nav-item-label {
|
||||
color: #141F38;
|
||||
}
|
||||
|
||||
&:nth-child(4) .nav-item-label,
|
||||
&:nth-child(5) .nav-item-label {
|
||||
color: #686868;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-main {
|
||||
flex: 1;
|
||||
padding-top: 46px;
|
||||
padding-bottom: 60px;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
&.nav-open {
|
||||
filter: blur(2px);
|
||||
}
|
||||
}
|
||||
|
||||
.mobile-footer {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #ffffff;
|
||||
border-top: 1px solid #e5e5e5;
|
||||
z-index: 1000;
|
||||
|
||||
.tab-bar {
|
||||
display: flex;
|
||||
height: 60px;
|
||||
|
||||
.tab-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
|
||||
&.active {
|
||||
color: #E7000B;
|
||||
}
|
||||
|
||||
&:not(.active) {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: 20px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 移动端特定样式
|
||||
@media (max-width: 767px) {
|
||||
.mobile-main {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user