fix: 右上角导航优化

This commit is contained in:
Gsh
2025-12-14 21:34:20 +08:00
parent da81b2d8a3
commit c7a52604e7
11 changed files with 423 additions and 87 deletions

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<g>
<path fill="none" d="M0 0h24v24H0z"/>
<path d="M12 2C16.9706 2 21 6.04348 21 11.0314V20H3V11.0314C3 6.04348 7.02944 2 12 2ZM9.5 21H14.5C14.5 22.3807 13.3807 23.5 12 23.5C10.6193 23.5 9.5 22.3807 9.5 21Z"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 322 B

View File

@@ -797,7 +797,7 @@ function generateShareContent(): string {
👉 点击链接立即参与我的专属邀请码链接: 👉 点击链接立即参与我的专属邀请码链接:
${shareLink} ${shareLink}
🍀 未注册用户,微信扫码登录,进入用户中心👉每周邀请 即可立即参与!`; 🍀 未注册用户,微信扫码登录,进入控制台👉每周邀请 即可立即参与!`;
} }
/** /**

View File

@@ -100,8 +100,8 @@ export function useGuideTour() {
{ {
element: '[data-tour="user-avatar"]', element: '[data-tour="user-avatar"]',
popover: { popover: {
title: '用户中心', title: '控制台',
description: '点击头像可以进入用户中心管理您的账户信息、查看使用统计、API密钥等。接下来将为您详细介绍用户中心的各项功能。', description: '点击头像可以进入控制台管理您的账户信息、查看使用统计、API密钥等。接下来将为您详细介绍用户中心的各项功能。',
side: 'bottom', side: 'bottom',
align: 'end', align: 'end',
}, },

View File

@@ -13,7 +13,7 @@ function openTutorial() {
@click="openTutorial" @click="openTutorial"
> >
<!-- PC端显示文字 --> <!-- PC端显示文字 -->
<span class="pc-text">AI使用教程</span> <span class="pc-text">文档</span>
<!-- 移动端显示图标 --> <!-- 移动端显示图标 -->
<svg <svg
class="mobile-icon w-6 h-6" class="mobile-icon w-6 h-6"

View File

@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import { Bell } from '@element-plus/icons-vue';
import { storeToRefs } from 'pinia'; import { storeToRefs } from 'pinia';
import { useAnnouncementStore } from '@/stores'; import { useAnnouncementStore } from '@/stores';
@@ -30,14 +29,27 @@ function openAnnouncement() {
<!-- :max="99" --> <!-- :max="99" -->
<div <div
class="announcement-btn" class="announcement-btn"
title="查看公告"
@click="openAnnouncement" @click="openAnnouncement"
> >
<!-- PC端显示文字 --> <!-- PC端显示文字 -->
<span class="pc-text">公告</span> <span class="pc-text">公告</span>
<!-- 移动端显示图标 --> <!-- 移动端显示图标 -->
<el-icon class="mobile-icon" :size="20"> <svg
<Bell /> class="mobile-icon"
</el-icon> xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9" />
<path d="M13.73 21a2 2 0 0 1-3.46 0" />
</svg>
</div> </div>
</el-badge> </el-badge>
</div> </div>

View File

@@ -2,14 +2,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { ChatLineRound } from '@element-plus/icons-vue'; import { ChatLineRound } from '@element-plus/icons-vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { computed, nextTick, onMounted, ref, watch } from 'vue'; import { nextTick, onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import Popover from '@/components/Popover/index.vue'; import Popover from '@/components/Popover/index.vue';
import SvgIcon from '@/components/SvgIcon/index.vue'; import SvgIcon from '@/components/SvgIcon/index.vue';
import { useGuideTour } from '@/hooks/useGuideTour'; import { useGuideTour } from '@/hooks/useGuideTour';
import { useGuideTourStore, useUserStore } from '@/stores'; import { useAnnouncementStore, useGuideTourStore, useUserStore } from '@/stores';
import { useSessionStore } from '@/stores/modules/session'; import { useSessionStore } from '@/stores/modules/session';
import { showProductPackage } from '@/utils/product-package';
import { getUserProfilePicture, isUserVip } from '@/utils/user'; import { getUserProfilePicture, isUserVip } from '@/utils/user';
const router = useRouter(); const router = useRouter();
@@ -17,15 +16,9 @@ const router = useRouter();
const userStore = useUserStore(); const userStore = useUserStore();
const sessionStore = useSessionStore(); const sessionStore = useSessionStore();
const guideTourStore = useGuideTourStore(); const guideTourStore = useGuideTourStore();
const announcementStore = useAnnouncementStore();
const { startUserCenterTour } = useGuideTour(); const { startUserCenterTour } = useGuideTour();
// const src = computed(
// () => userStore.userInfo?.avatar ?? 'https://avatars.githubusercontent.com/u/76239030',
// );
const src = computed(
() => userStore.userInfo?.user?.icon ? `${import.meta.env.VITE_WEB_BASE_API}/file/${userStore.userInfo.user.icon}` : `@/assets/images/logo.png`,
);
/* 弹出面板 开始 */ /* 弹出面板 开始 */
const popoverStyle = ref({ const popoverStyle = ref({
width: '200px', width: '200px',
@@ -36,21 +29,32 @@ const popoverRef = ref();
// 弹出面板内容 // 弹出面板内容
const popoverList = ref([ const popoverList = ref([
// {
// key: '1',
// title: '收藏夹',
// icon: 'book-mark-fill',
// },
// {
// key: '2',
// title: '设置',
// icon: 'settings-4-fill',
// },
{ {
key: '5', key: '5',
title: '用户中心', title: '控制台',
icon: 'settings-4-fill', icon: 'settings-4-fill',
}, },
{
key: '3',
divider: true,
},
{
key: '7',
title: '公告',
icon: 'notification-fill',
},
{
key: '8',
title: '模型库',
icon: 'apps-fill',
},
{
key: '9',
title: '文档',
icon: 'book-fill',
},
{ {
key: '6', key: '6',
title: '新手引导', title: '新手引导',
@@ -126,6 +130,21 @@ function handleClick(item: any) {
case '6': case '6':
handleStartTutorial(); handleStartTutorial();
break; break;
case '7':
// 打开公告
popoverRef.value?.hide?.();
announcementStore.openDialog();
break;
case '8':
// 打开模型库
popoverRef.value?.hide?.();
router.push('/model-library');
break;
case '9':
// 打开文档
popoverRef.value?.hide?.();
window.open('https://ccnetcore.com/article/3a1bc4d1-6a7d-751d-91cc-2817eb2ddcde', '_blank');
break;
case '4': case '4':
popoverRef.value?.hide?.(); popoverRef.value?.hide?.();
ElMessageBox.confirm('退出登录不会丢失任何数据,你仍可以登录此账号。', '确认退出登录?', { ElMessageBox.confirm('退出登录不会丢失任何数据,你仍可以登录此账号。', '确认退出登录?', {
@@ -200,11 +219,6 @@ function openVipGuide() {
}); });
} }
/* 弹出面板 结束 */
function onProductPackage() {
showProductPackage();
}
// ============ 监听对话框打开事件,切换到邀请码标签页 ============ // ============ 监听对话框打开事件,切换到邀请码标签页 ============
watch(dialogVisible, (newVal) => { watch(dialogVisible, (newVal) => {
if (newVal && externalInviteCode.value) { if (newVal && externalInviteCode.value) {
@@ -287,19 +301,17 @@ watch(() => guideTourStore.shouldStartUserCenterTour, (shouldStart) => {
}); });
} }
}); });
// ============ 暴露方法供外部调用 ============
defineExpose({
openDialog,
});
</script> </script>
<template> <template>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2 ">
<el-button
class="buy-btn flex items-center gap-2 px-5 py-2 font-semibold shadow-lg"
data-tour="buy-btn"
@click="onProductPackage"
>
<span>立即购买</span>
</el-button>
<!-- 用户信息区域 --> <!-- 用户信息区域 -->
<div class=" cursor-pointer flex flex-col text-right mr-2 leading-tight" @click="onProductPackage"> <div class="user-info-display cursor-pointer flex flex-col text-right mr-2 leading-tight" @click="openDialog">
<div class="text-sm font-semibold text-gray-800"> <div class="text-sm font-semibold text-gray-800">
{{ userStore.userInfo?.user.nick ?? '未登录用户' }} {{ userStore.userInfo?.user.nick ?? '未登录用户' }}
</div> </div>
@@ -382,7 +394,7 @@ watch(() => guideTourStore.shouldStartUserCenterTour, (shouldStart) => {
</div> </div>
<nav-dialog <nav-dialog
v-model="dialogVisible" v-model="dialogVisible"
title="用户中心" title="控制台"
:nav-items="navItems" :nav-items="navItems"
:default-active="activeNav" :default-active="activeNav"
@confirm="handleConfirm" @confirm="handleConfirm"
@@ -453,44 +465,4 @@ watch(() => guideTourStore.shouldStartUserCenterTour, (shouldStart) => {
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 16px rgb(0 0 0 / 8%); box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
} }
.buy-btn {
background: linear-gradient(90deg, #FFD700, #FFC107);
color: #fff;
border: none;
border-radius: 9999px;
transition: transform 0.2s, box-shadow 0.2s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(255, 215, 0, 0.5);
background: linear-gradient(90deg, #FFC107, #FFD700);
}
.icon-rocket {
color: #fff;
}
.animate-bounce {
animation: bounce 1.2s infinite;
}
}
//移动端屏幕小于756px
@media screen and (max-width: 756px) {
.buy-btn {
background: linear-gradient(90deg, #FFD700, #FFC107);
color: #fff;
border: none;
border-radius: 9999px;
transition: transform 0.2s, box-shadow 0.2s;
font-size: 12px;
max-width: 60px;
}
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
</style> </style>

View File

@@ -0,0 +1,68 @@
<script setup lang="ts">
import { showProductPackage } from '@/utils/product-package';
// 点击购买按钮
function onProductPackage() {
showProductPackage();
}
</script>
<template>
<div class="buy-btn-container">
<el-button
class="buy-btn flex items-center gap-2 px-5 py-2 font-semibold shadow-lg"
data-tour="buy-btn"
@click="onProductPackage"
>
<span>立即购买</span>
</el-button>
</div>
</template>
<style scoped lang="scss">
.buy-btn-container {
display: flex;
align-items: center;
margin: 0 22px 0 0;
.buy-btn {
background: linear-gradient(90deg, #FFD700, #FFC107);
color: #fff;
border: none;
border-radius: 9999px;
transition: transform 0.2s, box-shadow 0.2s;
&:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(255, 215, 0, 0.5);
background: linear-gradient(90deg, #FFC107, #FFD700);
}
.icon-rocket {
color: #fff;
}
.animate-bounce {
animation: bounce 1.2s infinite;
}
}
}
// 移动端屏幕小于756px
@media screen and (max-width: 756px) {
.buy-btn-container {
margin: 0 ;
.buy-btn {
font-size: 12px;
max-width: 60px;
padding: 8px 12px;
}
}
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
</style>

View File

@@ -0,0 +1,91 @@
<script setup lang="ts">
import { useUserStore } from '@/stores';
const userStore = useUserStore();
// 打开用户中心对话框(通过调用 Avatar 组件的方法)
function openConsole() {
// 触发事件,由父组件处理
emit('open-console');
}
const emit = defineEmits(['open-console']);
</script>
<template>
<div class="console-btn-container" data-tour="console-btn">
<div
class="console-btn"
title="打开控制台"
@click="openConsole"
>
<!-- PC端显示文字 -->
<span class="pc-text">控制台</span>
<!-- 移动端显示图标 -->
<svg
class="mobile-icon"
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
<line x1="8" y1="21" x2="16" y2="21" />
<line x1="12" y1="17" x2="12" y2="21" />
</svg>
</div>
</div>
</template>
<style scoped lang="scss">
.console-btn-container {
display: flex;
align-items: center;
.console-btn {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
font-size: 1.2rem;
font-weight: bold;
color: #606266;
transition: all 0.2s;
&:hover {
color: #909399;
transform: translateY(-1px);
}
// PC端显示文字隐藏图标
.pc-text {
display: inline;
margin: 0 12px;
}
.mobile-icon {
display: none;
}
}
}
// 移动端显示图标,隐藏文字
@media (max-width: 768px) {
.console-btn-container {
.console-btn {
.pc-text {
display: none;
}
.mobile-icon {
display: inline;
}
}
}
}
</style>

View File

@@ -7,7 +7,9 @@ import { useSessionStore } from '@/stores/modules/session';
import AiTutorialBtn from './components/AiTutorialBtn.vue'; import AiTutorialBtn from './components/AiTutorialBtn.vue';
import AnnouncementBtn from './components/AnnouncementBtn.vue'; import AnnouncementBtn from './components/AnnouncementBtn.vue';
import Avatar from './components/Avatar.vue'; import Avatar from './components/Avatar.vue';
import BuyBtn from './components/BuyBtn.vue';
import Collapse from './components/Collapse.vue'; import Collapse from './components/Collapse.vue';
import ConsoleBtn from './components/ConsoleBtn.vue';
import CreateChat from './components/CreateChat.vue'; import CreateChat from './components/CreateChat.vue';
import LoginBtn from './components/LoginBtn.vue'; import LoginBtn from './components/LoginBtn.vue';
import ModelLibraryBtn from './components/ModelLibraryBtn.vue'; import ModelLibraryBtn from './components/ModelLibraryBtn.vue';
@@ -17,6 +19,8 @@ const userStore = useUserStore();
const designStore = useDesignStore(); const designStore = useDesignStore();
const sessionStore = useSessionStore(); const sessionStore = useSessionStore();
const avatarRef = ref();
const currentSession = computed(() => sessionStore.currentSession); const currentSession = computed(() => sessionStore.currentSession);
onMounted(() => { onMounted(() => {
@@ -43,6 +47,11 @@ function handleCtrlK(event: KeyboardEvent) {
onKeyStroke(event => event.ctrlKey && event.key.toLowerCase() === 'k', handleCtrlK, { onKeyStroke(event => event.ctrlKey && event.key.toLowerCase() === 'k', handleCtrlK, {
passive: false, passive: false,
}); });
// 打开控制台
function handleOpenConsole() {
avatarRef.value?.openDialog?.();
}
</script> </script>
<template> <template>
@@ -75,7 +84,9 @@ onKeyStroke(event => event.ctrlKey && event.key.toLowerCase() === 'k', handleCtr
<AnnouncementBtn /> <AnnouncementBtn />
<ModelLibraryBtn /> <ModelLibraryBtn />
<AiTutorialBtn /> <AiTutorialBtn />
<Avatar v-show="userStore.userInfo" /> <ConsoleBtn @open-console="handleOpenConsole" />
<BuyBtn v-show="userStore.userInfo" />
<Avatar v-show="userStore.userInfo" ref="avatarRef" />
<LoginBtn v-show="!userStore.userInfo" /> <LoginBtn v-show="!userStore.userInfo" />
</div> </div>
</div> </div>

File diff suppressed because one or more lines are too long

View File

@@ -188,8 +188,8 @@ function contactCustomerService() {
<!-- 更多信息提示 --> <!-- 更多信息提示 -->
<div class="mb-6 text-gray-600 text-sm"> <div class="mb-6 text-gray-600 text-sm">
更多订单信息和会员详情<br>请前往 <strong>用户中心 充值记录</strong> 查看<br> 更多订单信息和会员详情<br>请前往 <strong>控制台 充值记录</strong> 查看<br>
用户中心在首页右上角个人头像点击下拉菜单 控制台在首页右上角个人头像点击下拉菜单
</div> </div>
<!-- 重新登录提示 --> <!-- 重新登录提示 -->