fix:appheader的样式切换
BIN
public/images/personal/切换_switch备份 2@2x.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/images/personal/切换_switch备份@2x.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
public/images/personal/未读消息_message-unread备份@2x.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
public/images/personal/用户_user备份 2@2x.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
public/images/personal/用户_user备份@2x.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
public/images/personal/矩形备份 35@2x.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
public/images/personal/退出_logout备份 2@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
public/images/personal/退出_logout备份 3@2x.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
@ -38,10 +38,6 @@
|
||||
<div class="nav-item" :class="{ active: activeKey === 'ai' }" @click="handleMenuSelect('ai')">
|
||||
<span class="nav-item-ai">AI体验</span>
|
||||
</div>
|
||||
|
||||
<!-- <div class="nav-item" :class="{ active: activeKey === 'practice' }" @click="handleMenuSelect('practice')">
|
||||
<span class="nav-item-practice">AI伴学</span>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 搜索框 -->
|
||||
@ -54,10 +50,10 @@
|
||||
|
||||
<!-- 移动端汉堡菜单按钮 -->
|
||||
<div class="mobile-menu-toggle" @click="toggleMobileMenu">
|
||||
<n-icon size="24">
|
||||
<NIcon size="24">
|
||||
<MenuOutline v-if="!mobileMenuOpen" />
|
||||
<CloseOutline v-else />
|
||||
</n-icon>
|
||||
</NIcon>
|
||||
</div>
|
||||
|
||||
<!-- 右侧操作区域 -->
|
||||
@ -84,12 +80,12 @@
|
||||
<span>{{ t('header.learningCenter') }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 管理端 -->
|
||||
<!-- 管理端
|
||||
<div class="action-item">
|
||||
<img src="/nav-icons/管理端.png" alt="" class="action-icon default-icon" />
|
||||
<img src="/nav-icons/管理端-选中.png" alt="" class="action-icon hover-icon" />
|
||||
<span>{{ t('header.management') }}</span>
|
||||
</div>
|
||||
</div> -->
|
||||
|
||||
<!-- 登录按钮 -->
|
||||
<div v-if="!userStore.isLoggedIn" class="auth-buttons" @click="showLoginModal">
|
||||
@ -100,15 +96,16 @@
|
||||
|
||||
<!-- 登录后的用户区域 -->
|
||||
<div v-else class="user-menu">
|
||||
<n-dropdown :options="userMenuOptions" @select="handleUserMenuSelect">
|
||||
<NDropdown :options="userMenuOptions" @select="handleUserMenuSelect">
|
||||
<div class="user-info">
|
||||
<SafeAvatar :src="userStore.user?.avatar"
|
||||
<SafeAvatar
|
||||
:src="userStore.user?.avatar"
|
||||
:name="userStore.user?.profile?.realName || userStore.user?.nickname || userStore.user?.username"
|
||||
:size="32" />
|
||||
<span class="username">{{ userStore.user?.profile?.realName || userStore.user?.nickname ||
|
||||
userStore.user?.username }}</span>
|
||||
:size="32"
|
||||
/>
|
||||
<span class="username">{{ userStore.user?.profile?.realName || userStore.user?.nickname || userStore.user?.username }}</span>
|
||||
</div>
|
||||
</n-dropdown>
|
||||
</NDropdown>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -117,14 +114,11 @@
|
||||
|
||||
<!-- 注册模态框 -->
|
||||
<RegisterModal v-model:show="registerModalVisible" @success="handleAuthSuccess" />
|
||||
|
||||
<!-- 内测通知模态框 -->
|
||||
<RegisterNotice v-model:show="registerNoticeVisible" @close="closeRegisterNotice" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, h, onMounted, onUnmounted, watch } from 'vue'
|
||||
import { computed, ref, onMounted, onUnmounted, h, watch } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
@ -133,9 +127,9 @@ import {
|
||||
MenuOutline,
|
||||
CloseOutline
|
||||
} from '@vicons/ionicons5'
|
||||
import { NDropdown, NIcon } from 'naive-ui'
|
||||
import LoginModal from '@/components/auth/LoginModal.vue'
|
||||
import RegisterModal from '@/components/auth/RegisterModal.vue'
|
||||
import RegisterNotice from '@/components/auth/RegisterNotice.vue'
|
||||
import SafeAvatar from '@/components/common/SafeAvatar.vue'
|
||||
|
||||
const router = useRouter()
|
||||
@ -195,7 +189,7 @@ const setActiveKeyFromRoute = () => {
|
||||
// 认证模态框相关
|
||||
const loginModalVisible = ref(false)
|
||||
const registerModalVisible = ref(false)
|
||||
const registerNoticeVisible = ref(false)
|
||||
|
||||
|
||||
// 语言切换相关
|
||||
const showLanguageDropdown = ref(false)
|
||||
@ -229,17 +223,150 @@ const handleLearningCenter = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 处理菜单项悬停效果
|
||||
const setupMenuHoverEffects = () => {
|
||||
const handleMouseEnter = (e: Event) => {
|
||||
const option = e.currentTarget as HTMLElement
|
||||
const container = option.querySelector('.menu-icon-container') as HTMLElement
|
||||
if (!container) return
|
||||
|
||||
const defaultIcon = container.querySelector('.default-icon') as HTMLElement
|
||||
const hoverIcon = container.querySelector('.hover-icon') as HTMLElement
|
||||
|
||||
// 图标切换
|
||||
if (defaultIcon) defaultIcon.style.opacity = '0'
|
||||
if (hoverIcon) hoverIcon.style.opacity = '1'
|
||||
|
||||
// 文字和背景颜色 - 使用更强的选择器
|
||||
option.style.setProperty('color', '#0088D1', 'important')
|
||||
option.style.setProperty('background', 'linear-gradient(135deg, #f0f8ff, #e6f4ff)', 'important')
|
||||
|
||||
// 查找所有可能的文字元素并设置颜色
|
||||
const allTextElements = option.querySelectorAll('*')
|
||||
allTextElements.forEach((el: Element) => {
|
||||
const element = el as HTMLElement
|
||||
if (element.textContent && element.textContent.trim()) {
|
||||
element.style.setProperty('color', '#0088D1', 'important')
|
||||
}
|
||||
})
|
||||
|
||||
console.log('悬停进入:', option.textContent?.trim())
|
||||
}
|
||||
|
||||
const handleMouseLeave = (e: Event) => {
|
||||
const option = e.currentTarget as HTMLElement
|
||||
const container = option.querySelector('.menu-icon-container') as HTMLElement
|
||||
if (!container) return
|
||||
|
||||
const defaultIcon = container.querySelector('.default-icon') as HTMLElement
|
||||
const hoverIcon = container.querySelector('.hover-icon') as HTMLElement
|
||||
|
||||
// 图标恢复
|
||||
if (defaultIcon) defaultIcon.style.opacity = '1'
|
||||
if (hoverIcon) hoverIcon.style.opacity = '0'
|
||||
|
||||
// 文字和背景恢复
|
||||
option.style.removeProperty('color')
|
||||
option.style.removeProperty('background')
|
||||
|
||||
// 恢复所有文字元素的颜色
|
||||
const allTextElements = option.querySelectorAll('*')
|
||||
allTextElements.forEach((el: Element) => {
|
||||
const element = el as HTMLElement
|
||||
element.style.removeProperty('color')
|
||||
})
|
||||
|
||||
console.log('悬停离开:', option.textContent?.trim())
|
||||
}
|
||||
|
||||
// 使用MutationObserver监听dropdown的渲染
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'childList') {
|
||||
const menuOptions = document.querySelectorAll('.n-dropdown-option')
|
||||
if (menuOptions.length > 0) {
|
||||
menuOptions.forEach((option) => {
|
||||
const container = option.querySelector('.menu-icon-container')
|
||||
if (container) {
|
||||
// 移除旧的事件监听器(如果存在)
|
||||
option.removeEventListener('mouseenter', handleMouseEnter)
|
||||
option.removeEventListener('mouseleave', handleMouseLeave)
|
||||
// 添加新的事件监听器
|
||||
option.addEventListener('mouseenter', handleMouseEnter)
|
||||
option.addEventListener('mouseleave', handleMouseLeave)
|
||||
}
|
||||
})
|
||||
console.log('菜单悬停事件已绑定')
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 开始观察body的变化
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
})
|
||||
|
||||
// 也尝试立即绑定(如果元素已存在)
|
||||
setTimeout(() => {
|
||||
const menuOptions = document.querySelectorAll('.n-dropdown-option')
|
||||
if (menuOptions.length > 0) {
|
||||
menuOptions.forEach((option) => {
|
||||
const container = option.querySelector('.menu-icon-container')
|
||||
if (container) {
|
||||
option.addEventListener('mouseenter', handleMouseEnter)
|
||||
option.addEventListener('mouseleave', handleMouseLeave)
|
||||
}
|
||||
})
|
||||
console.log('菜单悬停事件已立即绑定')
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// 用户菜单选项
|
||||
const userMenuOptions = computed(() => [
|
||||
{
|
||||
label: '个人中心',
|
||||
key: 'profile',
|
||||
icon: () => h('div', { class: 'custom-icon' }, '👤')
|
||||
icon: () => h('div', {
|
||||
class: 'menu-icon-container',
|
||||
style: 'position: relative; width: 18px; height: 18px; display: inline-block; margin-right: 1px; overflow: hidden;'
|
||||
}, [
|
||||
h('img', {
|
||||
src: '/images/personal/用户_user备份@2x.png',
|
||||
alt: '个人中心',
|
||||
class: 'menu-icon default-icon',
|
||||
style: 'width: 18px !important; height: 18px !important; max-width: 18px; max-height: 18px; object-fit: contain; position: absolute; top: 0; left: 0;'
|
||||
}),
|
||||
h('img', {
|
||||
src: '/images/personal/用户_user备份 2@2x.png',
|
||||
alt: '个人中心',
|
||||
class: 'menu-icon hover-icon',
|
||||
style: 'width: 18px !important; height: 18px !important; max-width: 18px; max-height: 18px; object-fit: contain; position: absolute; top: 0; left: 0; opacity: 0;'
|
||||
})
|
||||
])
|
||||
},
|
||||
{
|
||||
label: '切换教师端',
|
||||
key: 'teacher',
|
||||
icon: () => h('div', { class: 'custom-icon' }, '👨🏫')
|
||||
icon: () => h('div', {
|
||||
class: 'menu-icon-container',
|
||||
style: 'position: relative; width: 18px; height: 18px; display: inline-block; margin-right: 1px; overflow: hidden;'
|
||||
}, [
|
||||
h('img', {
|
||||
src: '/images/personal/切换_switch备份@2x.png',
|
||||
alt: '切换教师端',
|
||||
class: 'menu-icon default-icon',
|
||||
style: 'width: 18px !important; height: 18px !important; max-width: 18px; max-height: 18px; object-fit: contain; position: absolute; top: 0; left: 0;'
|
||||
}),
|
||||
h('img', {
|
||||
src: '/images/personal/切换_switch备份 2@2x.png',
|
||||
alt: '切换教师端',
|
||||
class: 'menu-icon hover-icon',
|
||||
style: 'width: 18px !important; height: 18px !important; max-width: 18px; max-height: 18px; object-fit: contain; position: absolute; top: 0; left: 0; opacity: 0;'
|
||||
})
|
||||
])
|
||||
},
|
||||
{
|
||||
type: 'divider'
|
||||
@ -247,10 +374,28 @@ const userMenuOptions = computed(() => [
|
||||
{
|
||||
label: '退出登录',
|
||||
key: 'logout',
|
||||
icon: () => h('div', { class: 'custom-icon' }, '🚪')
|
||||
icon: () => h('div', {
|
||||
class: 'menu-icon-container',
|
||||
style: 'position: relative; width: 18px; height: 18px; display: inline-block; margin-right: 1px; overflow: hidden;'
|
||||
}, [
|
||||
h('img', {
|
||||
src: '/images/personal/退出_logout备份 2@2x.png',
|
||||
alt: '退出登录',
|
||||
class: 'menu-icon default-icon',
|
||||
style: 'width: 18px !important; height: 18px !important; max-width: 18px; max-height: 18px; object-fit: contain; position: absolute; top: 0; left: 0;'
|
||||
}),
|
||||
h('img', {
|
||||
src: '/images/personal/退出_logout备份 3@2x.png',
|
||||
alt: '退出登录',
|
||||
class: 'menu-icon hover-icon',
|
||||
style: 'width: 18px !important; height: 18px !important; max-width: 18px; max-height: 18px; object-fit: contain; position: absolute; top: 0; left: 0; opacity: 0;'
|
||||
})
|
||||
])
|
||||
}
|
||||
])
|
||||
|
||||
// 菜单图标悬停效果现在完全由CSS处理
|
||||
|
||||
// 处理菜单选择
|
||||
const handleMenuSelect = (key: string) => {
|
||||
activeKey.value = key
|
||||
@ -324,10 +469,6 @@ const handleAuthSuccess = () => {
|
||||
console.log('认证成功')
|
||||
}
|
||||
|
||||
// 关闭内测通知
|
||||
const closeRegisterNotice = () => {
|
||||
registerNoticeVisible.value = false
|
||||
}
|
||||
|
||||
// 修复未定义的handleClickOutside
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
@ -342,6 +483,8 @@ onMounted(() => {
|
||||
document.addEventListener('click', handleClickOutside)
|
||||
// 初始化时根据当前路由设置激活状态
|
||||
setActiveKeyFromRoute()
|
||||
// 设置菜单悬停效果
|
||||
setupMenuHoverEffects()
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
@ -754,24 +897,168 @@ watch(() => route.path, () => {
|
||||
:deep(.n-dropdown-option) {
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
transition: all 0.2s ease;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
:deep(.n-dropdown-option:hover) {
|
||||
background: linear-gradient(135deg, #f0f8ff, #e6f4ff);
|
||||
color: #1890ff;
|
||||
/* 默认文字颜色 */
|
||||
:deep(.n-dropdown-option .n-dropdown-option__label) {
|
||||
color: #666666 !important;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
/* 强制覆盖Naive UI的默认样式 */
|
||||
:deep(.n-dropdown-option) {
|
||||
color: #666666 !important;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* 菜单图标容器样式 - 使用更强的选择器 */
|
||||
:deep(.menu-icon-container) {
|
||||
position: relative !important;
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
display: inline-block !important;
|
||||
margin-right: 8px !important;
|
||||
flex-shrink: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* 菜单图标样式 - 使用通配符强制覆盖所有图片 */
|
||||
:deep(.n-dropdown-option img),
|
||||
:deep(.n-dropdown-option .menu-icon-container img),
|
||||
:deep(.n-dropdown-option .menu-icon-container .menu-icon),
|
||||
:deep(.n-dropdown-option img.menu-icon),
|
||||
:deep(.menu-icon-container img),
|
||||
:deep(.menu-icon-container .menu-icon),
|
||||
:deep(img.menu-icon),
|
||||
:deep(.n-dropdown-option *) img {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
max-width: 18px !important;
|
||||
max-height: 18px !important;
|
||||
min-width: 18px !important;
|
||||
min-height: 18px !important;
|
||||
object-fit: contain !important;
|
||||
position: absolute !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
transition: opacity 0.2s ease !important;
|
||||
display: block !important;
|
||||
transform: scale(1) !important;
|
||||
}
|
||||
|
||||
/* 默认状态:显示默认图标,隐藏悬停图标 */
|
||||
:deep(.menu-icon-container img.default-icon),
|
||||
:deep(.menu-icon-container .menu-icon.default-icon),
|
||||
:deep(img.menu-icon.default-icon) {
|
||||
opacity: 1 !important;
|
||||
z-index: 2 !important;
|
||||
}
|
||||
|
||||
:deep(.menu-icon-container img.hover-icon),
|
||||
:deep(.menu-icon-container .menu-icon.hover-icon),
|
||||
:deep(img.menu-icon.hover-icon) {
|
||||
opacity: 0 !important;
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
/* 悬停时的背景和文字颜色效果 */
|
||||
:deep(.n-dropdown-option:hover) {
|
||||
background: linear-gradient(135deg, #f0f8ff, #e6f4ff) !important;
|
||||
color: #0088D1 !important;
|
||||
}
|
||||
|
||||
/* 悬停时文字颜色变化 - 使用多重选择器确保生效 */
|
||||
:deep(.n-dropdown-option:hover .n-dropdown-option__label),
|
||||
:deep(.n-dropdown-option:hover span),
|
||||
:deep(.n-dropdown-option:hover) {
|
||||
color: #0088D1 !important;
|
||||
}
|
||||
|
||||
/* 强制覆盖所有可能的文字颜色 */
|
||||
:deep(.n-dropdown-option:hover *:not(img)) {
|
||||
color: #0088D1 !important;
|
||||
}
|
||||
|
||||
/* 悬停时的图标切换效果 - 使用所有可能的选择器 */
|
||||
:deep(.n-dropdown-option:hover .menu-icon-container img.default-icon),
|
||||
:deep(.n-dropdown-option:hover .menu-icon-container .menu-icon.default-icon),
|
||||
:deep(.n-dropdown-option:hover img.menu-icon.default-icon) {
|
||||
opacity: 0 !important;
|
||||
}
|
||||
|
||||
:deep(.n-dropdown-option:hover .menu-icon-container img.hover-icon),
|
||||
:deep(.n-dropdown-option:hover .menu-icon-container .menu-icon.hover-icon),
|
||||
:deep(.n-dropdown-option:hover img.menu-icon.hover-icon) {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
/* 覆盖Naive UI的图标容器样式 */
|
||||
:deep(.n-dropdown-option .n-dropdown-option-icon) {
|
||||
margin-right: 12px;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin-right: 8px !important;
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
flex-shrink: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
/* 确保图标容器内的元素正确显示 */
|
||||
:deep(.n-dropdown-option .n-dropdown-option-icon .menu-icon-container) {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
}
|
||||
|
||||
/* 强制限制所有图片元素的尺寸 - 全局重置 */
|
||||
:deep(.n-dropdown-option img) {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
max-width: 18px !important;
|
||||
max-height: 18px !important;
|
||||
min-width: 18px !important;
|
||||
min-height: 18px !important;
|
||||
object-fit: contain !important;
|
||||
box-sizing: border-box !important;
|
||||
border: none !important;
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* 全局强制重置 - 覆盖任何可能的样式 */
|
||||
:deep(.n-dropdown) img,
|
||||
:deep(.n-dropdown-menu) img,
|
||||
:deep(.n-dropdown-option) img {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
max-width: 18px !important;
|
||||
max-height: 18px !important;
|
||||
min-width: 18px !important;
|
||||
min-height: 18px !important;
|
||||
}
|
||||
|
||||
/* 全局样式重置 - 不使用 :deep() */
|
||||
.n-dropdown img,
|
||||
.n-dropdown-menu img,
|
||||
.n-dropdown-option img {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
max-width: 18px !important;
|
||||
max-height: 18px !important;
|
||||
min-width: 18px !important;
|
||||
min-height: 18px !important;
|
||||
object-fit: contain !important;
|
||||
}
|
||||
|
||||
/* 针对用户菜单的特殊重置 */
|
||||
.user-menu img,
|
||||
.user-menu * img {
|
||||
width: 18px !important;
|
||||
height: 18px !important;
|
||||
max-width: 18px !important;
|
||||
max-height: 18px !important;
|
||||
}
|
||||
|
||||
.custom-icon {
|
||||
|