feat:新浮窗,和切换语言,和个人中心的字体统一

This commit is contained in:
小张 2025-09-07 17:50:03 +08:00
parent 21b76b9683
commit eb4c8504b1
2 changed files with 368 additions and 139 deletions

View File

@ -59,18 +59,23 @@
<!-- 右侧操作区域 -->
<div class="header-actions">
<!-- 切换语言 -->
<div class="action-item language-switcher" @click="toggleLanguageDropdown" ref="languageSwitcherRef">
<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.languageSwitch') }}</span>
<div v-if="showLanguageDropdown" class="language-dropdown">
<div class="language-option" @click.stop="switchLanguage('zh')">
<span class="language-text">{{ t('languageDropdown.switchToChinese') }}</span>
<div class="action-item language-switcher">
<NDropdown :options="languageOptions" @select="handleLanguageSelect">
<div class="language-trigger">
<div class="language-icon">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="12" cy="12" r="10" stroke="#666" stroke-width="2"/>
<path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" stroke="#666" stroke-width="2"/>
</svg>
</div>
<span class="language-text">{{ getCurrentLanguageLabel() }}</span>
<div class="language-arrow">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 9l6 6 6-6" stroke="#666" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
</div>
<div class="language-option" @click.stop="switchLanguage('en')">
<span class="language-text">{{ t('languageDropdown.switchToEnglish') }}</span>
</div>
</div>
</NDropdown>
</div>
<!-- 学习中心 -->
@ -195,6 +200,52 @@ const registerModalVisible = ref(false)
const showLanguageDropdown = ref(false)
const languageSwitcherRef = ref<HTMLElement | null>(null)
//
const languageOptions = computed(() => [
{
label: '中文',
key: 'zh',
props: {
onClick: () => handleLanguageSelect('zh')
}
},
{
label: 'English',
key: 'en',
props: {
onClick: () => handleLanguageSelect('en')
}
},
{
label: 'Русский',
key: 'ru',
props: {
onClick: () => handleLanguageSelect('ru')
}
},
{
label: 'Français',
key: 'fr',
props: {
onClick: () => handleLanguageSelect('fr')
}
},
{
label: 'Español',
key: 'es',
props: {
onClick: () => handleLanguageSelect('es')
}
},
{
label: '日本語',
key: 'ja',
props: {
onClick: () => handleLanguageSelect('ja')
}
}
])
//
const toggleMobileMenu = () => {
mobileMenuOpen.value = !mobileMenuOpen.value
@ -205,12 +256,34 @@ const toggleLanguageDropdown = () => {
showLanguageDropdown.value = !showLanguageDropdown.value
}
//
const switchLanguage = (lang: string) => {
//
const handleLanguageSelect = (lang: string) => {
locale.value = lang
localStorage.setItem('locale', lang)
showLanguageDropdown.value = false
console.log('语言已切换到:', lang === 'zh' ? '中文' : '英文')
console.log('语言已切换到:', getLanguageName(lang))
}
//
const getCurrentLanguageLabel = () => {
return getLanguageName(locale.value)
}
//
const getLanguageName = (lang: string): string => {
const languageMap: { [key: string]: string } = {
'zh': '中文',
'en': 'English',
'ru': 'Русский',
'fr': 'Français',
'es': 'Español',
'ja': '日本語'
}
return languageMap[lang] || '中文'
}
//
const switchLanguage = (lang: string) => {
handleLanguageSelect(lang)
}
const handleLearningCenter = () => {
@ -324,72 +397,146 @@ const setupMenuHoverEffects = () => {
}, 500)
}
//
// -
const userMenuOptions = computed(() => [
{
label: '个人中心',
key: 'profile',
icon: () => h('div', {
class: 'menu-icon-container',
style: 'position: relative; width: 18px; height: 18px; display: inline-block; margin-right: 1px; overflow: hidden;'
type: 'render',
render: () => h('div', {
class: 'custom-user-menu',
style: 'padding: 16px 24px; width: 280px;'
}, [
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: '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'
},
{
label: '退出登录',
key: 'logout',
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;'
})
//
h('div', {
class: 'user-menu-header',
style: 'display: flex; align-items: center; margin-bottom: 16px;'
}, [
h('span', {
style: 'font-size: 14px; color: #333; font-weight: 500;'
}, `Hi~ ${userStore.user?.profile?.realName || userStore.user?.nickname || userStore.user?.username || 'U2per'}`)
]),
// 2x2
h('div', {
class: 'user-menu-grid',
style: 'display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 16px;'
}, [
//
h('div', {
class: 'menu-grid-item',
style: 'display: flex; align-items: center; padding: 10px 12px; border-radius: 6px; cursor: pointer; transition: background-color 0.2s; background-color: #f9f9f9;',
onClick: () => handleUserMenuSelect('courses')
}, [
h('div', {
class: 'menu-icon-container',
style: 'margin-right: 8px;'
}, [
h('img', {
src: '/images/personal/用户_user备份@2x.png',
alt: '我的课程',
style: 'width: 18px; height: 18px; object-fit: contain;'
})
]),
h('span', {
style: 'font-size: 14px; color: #333; font-weight: 500;'
}, '我的课程')
]),
//
h('div', {
class: 'menu-grid-item',
style: 'display: flex; align-items: center; padding: 10px 12px; border-radius: 6px; cursor: pointer; transition: background-color 0.2s; background-color: #f9f9f9;',
onClick: () => handleUserMenuSelect('purchase')
}, [
h('div', {
class: 'menu-icon-container',
style: 'margin-right: 8px;'
}, [
h('img', {
src: '/images/personal/用户_user备份@2x.png',
alt: '购买记录',
style: 'width: 18px; height: 18px; object-fit: contain;'
})
]),
h('span', {
style: 'font-size: 14px; color: #333; font-weight: 500;'
}, '购买记录')
]),
//
h('div', {
class: 'menu-grid-item',
style: 'display: flex; align-items: center; padding: 10px 12px; border-radius: 6px; cursor: pointer; transition: background-color 0.2s; background-color: #f9f9f9;',
onClick: () => handleUserMenuSelect('coins')
}, [
h('div', {
class: 'menu-icon-container',
style: 'margin-right: 8px;'
}, [
h('img', {
src: '/images/personal/切换_switch备份@2x.png',
alt: '我的学币',
style: 'width: 18px; height: 18px; object-fit: contain;'
})
]),
h('span', {
style: 'font-size: 14px; color: #333; font-weight: 500;'
}, '我的学币')
]),
//
h('div', {
class: 'menu-grid-item',
style: 'display: flex; align-items: center; padding: 10px 12px; border-radius: 6px; cursor: pointer; transition: background-color 0.2s; background-color: #f9f9f9;',
onClick: () => handleUserMenuSelect('profile')
}, [
h('div', {
class: 'menu-icon-container',
style: 'margin-right: 8px;'
}, [
h('img', {
src: '/images/personal/用户_user备份@2x.png',
alt: '个人资料',
style: 'width: 18px; height: 18px; object-fit: contain;'
})
]),
h('span', {
style: 'font-size: 14px; color: #333; font-weight: 500;'
}, '个人资料')
])
]),
//
h('div', {
class: 'user-menu-footer',
style: 'display: flex; justify-content: space-between; align-items: center; padding-top: 12px; border-top: 1px solid #f0f0f0;'
}, [
//
h('div', {
class: 'footer-action',
style: 'display: flex; align-items: center; gap: 4px; cursor: pointer; padding: 4px 8px; border-radius: 4px; transition: background-color 0.2s;',
onClick: () => handleUserMenuSelect('teacher')
}, [
h('img', {
src: '/images/personal/切换_switch备份@2x.png',
alt: '切换教师端',
style: 'width: 14px; height: 14px; object-fit: contain;'
}),
h('span', {
style: 'font-size: 12px; color: #666;'
}, '切换教师端')
]),
// 退
h('div', {
class: 'footer-action',
style: 'display: flex; align-items: center; gap: 4px; cursor: pointer; padding: 4px 8px; border-radius: 4px; transition: background-color 0.2s;',
onClick: () => handleUserMenuSelect('logout')
}, [
h('span', {
style: 'font-size: 12px; color: #666;'
}, '安全退出')
])
])
])
}
])
@ -433,6 +580,22 @@ const handleMenuSelect = (key: string) => {
//
const handleUserMenuSelect = (key: string) => {
switch (key) {
case 'courses':
//
router.push('/profile/courses').then(() => {
window.location.reload();
})
break
case 'purchase':
//
console.log('跳转到购买记录')
//
break
case 'coins':
//
console.log('跳转到我的学币')
//
break
case 'profile':
router.push('/profile').then(() => {
// sessionStorage
@ -784,45 +947,46 @@ watch(() => route.path, () => {
/* 语言切换器 */
.language-switcher {
position: relative;
cursor: pointer;
}
.language-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #e8e8e8;
border-radius: 6px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
z-index: 1000;
margin-top: 4px;
min-width: 120px;
}
.language-option {
padding: 8px 12px;
font-size: 13px;
color: #666;
.language-trigger {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
padding: 6px 8px;
border-radius: 4px;
transition: all 0.2s;
border-bottom: 1px solid #f5f5f5;
}
.language-option:last-child {
border-bottom: none;
.language-trigger:hover {
background-color: #f0f8ff;
}
.language-option:hover {
background: #f0f8ff;
color: #1890ff;
.language-icon {
display: flex;
align-items: center;
justify-content: center;
}
.language-text {
font-size: 14px;
color: #1890ff;
font-weight: 500;
white-space: nowrap;
}
.language-arrow {
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s;
}
.language-switcher:hover .language-arrow {
transform: rotate(180deg);
}
/* 认证按钮 */
.auth-buttons {
display: flex;
@ -893,10 +1057,28 @@ watch(() => route.path, () => {
/* 美化用户菜单样式 */
:deep(.n-dropdown-menu) {
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
border: 1px solid #f0f0f0;
padding: 8px 0;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
border: 1px solid #e6e6e6;
padding: 0;
min-width: 280px;
}
/* 自定义用户菜单样式 */
.custom-user-menu {
background: white;
}
.user-menu-header {
font-weight: 500;
}
.menu-grid-item:hover {
background-color: #eeeeee !important;
}
.footer-action:hover {
background-color: #f5f5f5 !important;
}
:deep(.n-dropdown-option) {

View File

@ -770,12 +770,12 @@
<img src="/images/traings/traing1.png" class="image_22" />
<div class="user-info">
<div class="user-content">
<span class="message-text" :class="{ 'mention-highlight': msg.senderName.includes('@') }">{{
msg.senderName }}</span>
<span class="message-text">{{ msg.content }}</span>
<span class="user-name">{{ msg.senderName }}</span>
<span class="action-type">{{ getActionText(msg.type) }}</span>
<span class="message-content">{{ msg.content }}</span>
</div>
<div class="course-info-container">
<span class="course-label">回复我的课程</span>
<span class="course-label">回复我的课程</span>
<span class="course-name">{{ msg.courseName }}</span>
</div>
</div>
@ -826,17 +826,15 @@
<div class="like-message-user">
<img :src="like.userAvatar" class="image_22" />
<div class="like-info">
<div class="like-content">
<span class="message-text">{{ like.userName }}</span>
<span class="message-text">赞了我的{{ like.type === 'comment' ? '评论' : '课程' }}</span>
<div class="user-content">
<span class="user-name">{{ like.userName }}</span>
<span class="action-type">{{ getLikeActionText(like.type) }}</span>
<span class="message-content" v-if="like.content">{{ like.content }}</span>
</div>
<div class="course-info-container" v-if="like.courseName">
<span class="course-label">课程</span>
<span class="course-label">课程</span>
<span class="course-name">{{ like.courseName }}</span>
</div>
<div class="like-content-preview" v-if="like.content">
<span class="content-preview">{{ like.content }}</span>
</div>
</div>
<div class="message-time">{{ like.date }}</div>
</div>
@ -1260,6 +1258,7 @@ interface Message {
isRead: boolean
hasReply: boolean
replyContent?: string
type: 'comment' | 'mention' | 'reply'
}
//
@ -2066,34 +2065,37 @@ const mockMessages: Message[] = [
id: 1,
senderName: '王建',
senderAvatar: '/images/avatars/teacher1.png',
content: '回复了我:没事多看看课程的起源',
content: '没事多看看课程的起源',
courseName: '教育心理学的起源',
date: '7月20日 12:41',
isRead: false,
hasReply: true,
replyContent: ''
replyContent: '',
type: 'comment'
},
{
id: 2,
senderName: '建国学习@了我:',
senderName: '建国学习',
senderAvatar: '/images/avatars/student1.png',
content: '没事多看看课程的起源',
courseName: '教育心理学的起源',
date: '7月20日 12:41',
isRead: false,
hasReply: true,
replyContent: ''
replyContent: '',
type: 'mention'
},
{
id: 3,
senderName: '建国学习',
senderAvatar: '/images/avatars/student2.png',
content: '回复了我:没事多看看课程的起源了',
content: '没事多看看课程的起源了',
courseName: '教育心理学的起源',
date: '7月20日 12:41',
isRead: false,
hasReply: true,
replyContent: ''
replyContent: '',
type: 'reply'
}
]
@ -2547,6 +2549,32 @@ const handleMessageTabChange = (tab: string) => {
activeMessageTab.value = tab
}
//
const getActionText = (type: string) => {
switch (type) {
case 'comment':
return '评论了我'
case 'mention':
return '@了我'
case 'reply':
return '回复了我'
default:
return '评论了我'
}
}
//
const getLikeActionText = (type: string) => {
switch (type) {
case 'comment':
return '点赞了我的评论'
case 'course':
return '点赞了我'
default:
return '点赞了我'
}
}
//
const handleMaterialsTabChange = (tab: string) => {
activeMaterialsTab.value = tab
@ -6328,13 +6356,34 @@ onActivated(() => {
/* 隐藏溢出内容 */
}
/* 新的消息样式 */
.user-name {
font-size: 0.83vw;
/* 16px转换为vw */
font-weight: 500;
color: #1890ff;
font-family: PingFangSC, PingFang SC, -apple-system, BlinkMacSystemFont, sans-serif;
font-weight: 400;
font-size: 14px;
color: #0388D1;
line-height: 20px;
}
.action-type {
font-family: PingFangSC, PingFang SC, -apple-system, BlinkMacSystemFont, sans-serif;
font-weight: 400;
font-size: 14px;
color: #0388D1;
line-height: 20px;
margin-right: 4px;
}
.message-content {
font-family: PingFangSC, PingFang SC, -apple-system, BlinkMacSystemFont, sans-serif;
font-weight: 400;
font-size: 14px;
color: #000000;
line-height: 20px;
}
.message-text {
width: auto;
/* 自适应宽度 */
@ -6393,13 +6442,11 @@ onActivated(() => {
}
.course-name {
font-size: 0.73vw;
/* 14px转换为vw */
font-family: Helvetica, 'Microsoft YaHei', Arial, sans-serif;
font-weight: normal;
/* color: #497087; */
line-height: 1.04vh;
/* 20px转换为vh */
font-family: PingFangSC, PingFang SC, -apple-system, BlinkMacSystemFont, sans-serif;
font-weight: 400;
font-size: 14px;
color: #608297;
line-height: 20px;
}
.message-time {