mirror of
https://gitee.com/ccnetcore/Yi
synced 2026-04-10 19:26:39 +08:00
fix: 前端页面架构重构初版
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
<script setup lang="ts">
|
||||
// 打开AI使用教程(跳转到外部链接)
|
||||
function openTutorial() {
|
||||
window.open('https://ccnetcore.com/article/3a1bc4d1-6a7d-751d-91cc-2817eb2ddcde', '_blank');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ai-tutorial-btn-container" data-tour="ai-tutorial-link">
|
||||
<div
|
||||
class="ai-tutorial-btn"
|
||||
title="点击跳转YiXinAI玩法指南专栏"
|
||||
@click="openTutorial"
|
||||
>
|
||||
<!-- PC端显示文字 -->
|
||||
<span class="pc-text">文档</span>
|
||||
<!-- 移动端显示图标 -->
|
||||
<svg
|
||||
class="mobile-icon w-6 h-6"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 14l9-5-9-5-9 5 9 5z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 14l6.16-3.422A12.083 12.083 0 0118 13.5c0 2.579-3.582 4.5-6 4.5s-6-1.921-6-4.5c0-.432.075-.85.198-1.244L12 14z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ai-tutorial-btn-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.ai-tutorial-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #E6A23C;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #F1B44C;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
// PC端显示文字,隐藏图标
|
||||
.pc-text {
|
||||
display: inline;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.mobile-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端显示图标,隐藏文字
|
||||
@media (max-width: 768px) {
|
||||
.ai-tutorial-btn-container {
|
||||
.ai-tutorial-btn {
|
||||
.pc-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-icon {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,112 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useAnnouncementStore } from '@/stores';
|
||||
|
||||
const announcementStore = useAnnouncementStore();
|
||||
const { announcements } = storeToRefs(announcementStore);
|
||||
|
||||
// 计算未读公告数量(系统公告数量)
|
||||
const unreadCount = computed(() => {
|
||||
if (!Array.isArray(announcements.value))
|
||||
return 0;
|
||||
return announcements.value.filter(a => a.type === 'System').length;
|
||||
});
|
||||
|
||||
// 打开公告弹窗
|
||||
function openAnnouncement() {
|
||||
announcementStore.openDialog();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="announcement-btn-container" data-tour="announcement-btn">
|
||||
<el-badge
|
||||
is-dot
|
||||
class="announcement-badge"
|
||||
>
|
||||
<!-- :value="unreadCount" -->
|
||||
<!-- :hidden="unreadCount === 0" -->
|
||||
<!-- :max="99" -->
|
||||
<div
|
||||
class="announcement-btn"
|
||||
title="查看公告"
|
||||
@click="openAnnouncement"
|
||||
>
|
||||
<!-- 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"
|
||||
>
|
||||
<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>
|
||||
</el-badge>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.announcement-btn-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.announcement-badge {
|
||||
:deep(.el-badge__content) {
|
||||
background-color: #f56c6c;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.announcement-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #66b1ff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
// PC端显示文字,隐藏图标
|
||||
.pc-text {
|
||||
display: inline;
|
||||
margin: 0 12px;
|
||||
|
||||
}
|
||||
|
||||
.mobile-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端显示图标,隐藏文字
|
||||
@media (max-width: 768px) {
|
||||
.announcement-btn-container {
|
||||
.announcement-btn {
|
||||
.pc-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-icon {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
499
Yi.Ai.Vue3/src/layouts/components0/Header/components/Avatar.vue
Normal file
499
Yi.Ai.Vue3/src/layouts/components0/Header/components/Avatar.vue
Normal file
@@ -0,0 +1,499 @@
|
||||
<!-- 头像 -->
|
||||
<script setup lang="ts">
|
||||
import { ChatLineRound } from '@element-plus/icons-vue';
|
||||
import { ElMessage, ElMessageBox } from 'element-plus';
|
||||
import { nextTick, onMounted, ref, watch } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import Popover from '@/components/Popover/index.vue';
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
import { useGuideTour } from '@/hooks/useGuideTour';
|
||||
import { useAnnouncementStore, useGuideTourStore, useUserStore } from '@/stores';
|
||||
import { useSessionStore } from '@/stores/modules/session';
|
||||
import { getUserProfilePicture, isUserVip } from '@/utils/user';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const userStore = useUserStore();
|
||||
const sessionStore = useSessionStore();
|
||||
const guideTourStore = useGuideTourStore();
|
||||
const announcementStore = useAnnouncementStore();
|
||||
const { startUserCenterTour } = useGuideTour();
|
||||
|
||||
/* 弹出面板 开始 */
|
||||
const popoverStyle = ref({
|
||||
width: '200px',
|
||||
padding: '4px',
|
||||
height: 'fit-content',
|
||||
});
|
||||
const popoverRef = ref();
|
||||
|
||||
// 弹出面板内容
|
||||
const popoverList = ref([
|
||||
|
||||
{
|
||||
key: '5',
|
||||
title: '控制台',
|
||||
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',
|
||||
title: '新手引导',
|
||||
icon: 'dashboard-fill',
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
key: '4',
|
||||
title: '退出登录',
|
||||
icon: 'logout-box-r-line',
|
||||
},
|
||||
]);
|
||||
|
||||
const dialogVisible = ref(false);
|
||||
const rechargeLogRef = ref();
|
||||
const activeNav = ref('user');
|
||||
|
||||
// ============ 邀请码分享功能 ============
|
||||
/** 从 URL 获取的邀请码 */
|
||||
const externalInviteCode = ref<string>('');
|
||||
|
||||
const navItems = [
|
||||
{ name: 'user', label: '用户信息', icon: 'User' },
|
||||
// { name: 'role', label: '角色管理', icon: 'Avatar' },
|
||||
// { name: 'permission', label: '权限管理', icon: 'Key' },
|
||||
// { name: 'userInfo', label: '用户信息', icon: 'User' },
|
||||
{ name: 'apiKey', label: 'API密钥', icon: 'Key' },
|
||||
|
||||
{ name: 'rechargeLog', label: '充值记录', icon: 'Document' },
|
||||
{ name: 'usageStatistics', label: '用量统计', icon: 'Histogram' },
|
||||
{ name: 'premiumService', label: '尊享服务', icon: 'ColdDrink' },
|
||||
{ name: 'dailyTask', label: '每日任务(限时)', icon: 'Trophy' },
|
||||
{ name: 'cardFlip', label: '每周邀请(限时)', icon: 'Present' },
|
||||
// { name: 'usageStatistics2', label: '用量统计2', icon: 'Histogram' },
|
||||
{ name: 'activationCode', label: '激活码兑换', icon: 'MagicStick' },
|
||||
];
|
||||
function openDialog() {
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
function handleConfirm(activeNav: string) {
|
||||
ElMessage.success('操作成功');
|
||||
}
|
||||
|
||||
// 导航切换
|
||||
function handleNavChange(nav: string) {
|
||||
activeNav.value = nav;
|
||||
// 同步更新 store 中的 tab 状态,防止下次通过 store 打开同一 tab 时因值未变而不触发 watch
|
||||
if (userStore.userCenterActiveTab !== nav) {
|
||||
userStore.userCenterActiveTab = nav;
|
||||
}
|
||||
}
|
||||
|
||||
// 联系售后
|
||||
function handleContactSupport() {
|
||||
rechargeLogRef.value?.contactCustomerService();
|
||||
}
|
||||
const { startHeaderTour } = useGuideTour();
|
||||
|
||||
// 开始引导教程
|
||||
function handleStartTutorial() {
|
||||
startHeaderTour();
|
||||
}
|
||||
// 点击
|
||||
function handleClick(item: any) {
|
||||
switch (item.key) {
|
||||
case '1':
|
||||
ElMessage.warning('暂未开放');
|
||||
break;
|
||||
case '2':
|
||||
ElMessage.warning('暂未开放');
|
||||
break;
|
||||
case '5':
|
||||
// 打开控制台
|
||||
popoverRef.value?.hide?.();
|
||||
router.push('/console');
|
||||
break;
|
||||
case '6':
|
||||
handleStartTutorial();
|
||||
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':
|
||||
popoverRef.value?.hide?.();
|
||||
ElMessageBox.confirm('退出登录不会丢失任何数据,你仍可以登录此账号。', '确认退出登录?', {
|
||||
confirmButtonText: '确认退出',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
confirmButtonClass: 'el-button--danger',
|
||||
cancelButtonClass: 'el-button--info',
|
||||
roundButton: true,
|
||||
autofocus: false,
|
||||
})
|
||||
.then(async () => {
|
||||
// 在这里执行退出方法
|
||||
await userStore.logout();
|
||||
// 清空回话列表并回到默认页
|
||||
await sessionStore.requestSessionList(1, true);
|
||||
await sessionStore.createSessionBtn();
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '退出成功',
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// ElMessage({
|
||||
// type: 'info',
|
||||
// message: '取消',
|
||||
// });
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function openVipGuide() {
|
||||
ElMessageBox.confirm(
|
||||
`
|
||||
<div class="text-center leading-relaxed">
|
||||
<h3 class="text-lg font-bold mb-3">${isUserVip() ? 'YiXinAI-VIP 会员' : '成为 YiXinAI-VIP'}</h3>
|
||||
<p class="mb-2">
|
||||
${
|
||||
isUserVip()
|
||||
? '您已是尊贵会员,享受全部 AI 模型与专属服务。感谢支持!'
|
||||
: '解锁所有 AI 模型,无限加速,专属客服,尽享尊贵体验。'
|
||||
}
|
||||
</p>
|
||||
${
|
||||
isUserVip()
|
||||
? '<p class="text-sm text-gray-500">您可随时访问产品页面查看更多特权内容。</p>'
|
||||
: '<p class="text-sm text-gray-500">点击下方按钮,立即升级为 VIP 会员!</p>'
|
||||
}
|
||||
</div>
|
||||
`,
|
||||
isUserVip() ? '会员状态' : '会员尊享',
|
||||
{
|
||||
confirmButtonText: '前往产品页面',
|
||||
cancelButtonText: '关闭',
|
||||
dangerouslyUseHTMLString: true,
|
||||
type: 'info',
|
||||
center: true,
|
||||
roundButton: true,
|
||||
},
|
||||
)
|
||||
.then(() => {
|
||||
router.push({
|
||||
name: 'products', // 使用命名路由
|
||||
query: { from: isUserVip() ? 'vip' : 'user' }, // 可选:添加来源标识
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// 点击右上角关闭或“关闭”按钮,不执行任何操作
|
||||
});
|
||||
}
|
||||
|
||||
// ============ 监听对话框打开事件,切换到邀请码标签页 ============
|
||||
watch(dialogVisible, (newVal) => {
|
||||
if (newVal && externalInviteCode.value) {
|
||||
// 对话框打开后,切换标签页(已通过 :default-active 绑定,会自动响应)
|
||||
// console.log('[Avatar] watch: 对话框已打开,切换到 cardFlip 标签页');
|
||||
nextTick(() => {
|
||||
activeNav.value = 'cardFlip';
|
||||
// console.log('[Avatar] watch: 已设置 activeNav 为', activeNav.value);
|
||||
});
|
||||
}
|
||||
|
||||
// 对话框关闭时,清除邀请码状态和 URL 参数
|
||||
if (!newVal && externalInviteCode.value) {
|
||||
// console.log('[Avatar] watch: 对话框关闭,清除邀请码状态');
|
||||
externalInviteCode.value = '';
|
||||
|
||||
// 清除 URL 中的 inviteCode 参数
|
||||
const url = new URL(window.location.href);
|
||||
if (url.searchParams.has('inviteCode')) {
|
||||
url.searchParams.delete('inviteCode');
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
// console.log('[Avatar] watch: 已清除 URL 中的 inviteCode 参数');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ============ 监听 URL 参数,实现邀请码快捷分享 ============
|
||||
onMounted(() => {
|
||||
// 获取 URL 查询参数
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const inviteCode = urlParams.get('inviteCode');
|
||||
|
||||
if (inviteCode && inviteCode.trim()) {
|
||||
// console.log('[Avatar] onMounted: 检测到邀请码', inviteCode);
|
||||
|
||||
// 保存邀请码
|
||||
externalInviteCode.value = inviteCode.trim();
|
||||
|
||||
// 先设置标签页为 cardFlip
|
||||
activeNav.value = 'cardFlip';
|
||||
// console.log('[Avatar] onMounted: 设置 activeNav 为', activeNav.value);
|
||||
|
||||
// 延迟打开对话框,确保状态已更新
|
||||
nextTick(() => {
|
||||
setTimeout(() => {
|
||||
// console.log('[Avatar] onMounted: 打开用户中心对话框');
|
||||
dialogVisible.value = true;
|
||||
}, 200);
|
||||
});
|
||||
|
||||
// 注意:不立即清除 URL 参数,保留给登录后使用
|
||||
// URL 参数会在对话框关闭时清除
|
||||
}
|
||||
});
|
||||
|
||||
// ============ 监听引导状态,自动打开用户中心并开始引导 ============
|
||||
watch(() => guideTourStore.shouldStartUserCenterTour, (shouldStart) => {
|
||||
if (shouldStart) {
|
||||
// 清除触发标记
|
||||
guideTourStore.clearUserCenterTourTrigger();
|
||||
|
||||
// 注册导航切换回调
|
||||
guideTourStore.setUserCenterNavChangeCallback((nav: string) => {
|
||||
activeNav.value = nav;
|
||||
});
|
||||
|
||||
// 注册关闭弹窗回调
|
||||
guideTourStore.setUserCenterCloseCallback(() => {
|
||||
dialogVisible.value = false;
|
||||
});
|
||||
|
||||
// 打开用户中心弹窗
|
||||
nextTick(() => {
|
||||
dialogVisible.value = true;
|
||||
|
||||
// 等待弹窗打开后开始引导
|
||||
setTimeout(() => {
|
||||
startUserCenterTour();
|
||||
}, 600);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ============ 监听 Store 状态,控制用户中心弹窗 (新增) ============
|
||||
watch(() => userStore.isUserCenterVisible, (val) => {
|
||||
dialogVisible.value = val;
|
||||
if (val && userStore.userCenterActiveTab) {
|
||||
activeNav.value = userStore.userCenterActiveTab;
|
||||
}
|
||||
});
|
||||
|
||||
watch(() => userStore.userCenterActiveTab, (val) => {
|
||||
if (val) {
|
||||
activeNav.value = val;
|
||||
}
|
||||
});
|
||||
|
||||
// 监听本地 dialogVisible 变化,同步回 Store(可选,为了保持一致性)
|
||||
watch(dialogVisible, (val) => {
|
||||
if (!val) {
|
||||
userStore.closeUserCenter();
|
||||
}
|
||||
});
|
||||
|
||||
// ============ 暴露方法供外部调用 ============
|
||||
defineExpose({
|
||||
openDialog,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center gap-2 ">
|
||||
<!-- 用户信息区域 -->
|
||||
<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">
|
||||
{{ userStore.userInfo?.user.nick ?? '未登录用户' }}
|
||||
</div>
|
||||
|
||||
<!-- 角色展示 -->
|
||||
<div>
|
||||
<span
|
||||
v-if="isUserVip()"
|
||||
class="inline-block px-2 py-0.5 text-xs text-yellow-700 bg-yellow-100 rounded-full font-semibold"
|
||||
>
|
||||
YiXinAI-VIP
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-else
|
||||
class="inline-block px-2 py-0.5 text-xs text-gray-600 bg-gray-100 rounded-full cursor-pointer hover:bg-yellow-50 hover:text-yellow-700 transition"
|
||||
>
|
||||
普通用户
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 头像区域 -->
|
||||
<div class="avatar-container" data-tour="user-avatar">
|
||||
<Popover
|
||||
ref="popoverRef"
|
||||
placement="bottom-end"
|
||||
trigger="clickTarget"
|
||||
:trigger-style="{ cursor: 'pointer' }"
|
||||
popover-class="popover-content"
|
||||
:popover-style="popoverStyle"
|
||||
>
|
||||
<template #trigger>
|
||||
<el-avatar :src="getUserProfilePicture()" :size="28" fit="fit" shape="circle" />
|
||||
</template>
|
||||
|
||||
<div class="popover-content-box shadow-lg">
|
||||
<!-- 用户信息 -->
|
||||
<div class="user-info-box flex items-center gap-8px p-8px rounded-lg mb-2">
|
||||
<el-avatar :src="getUserProfilePicture()" :size="32" fit="fit" shape="circle" />
|
||||
<div class="flex flex-col text-sm">
|
||||
<div class="font-semibold text-gray-800">
|
||||
{{ userStore.userInfo?.user.nick ?? '未登录用户' }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
<span
|
||||
v-if="isUserVip()"
|
||||
class="inline-block px-2 py-0.5 text-xs text-yellow-700 bg-yellow-100 rounded-full font-semibold"
|
||||
>
|
||||
YiXinAI-VIP
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-else
|
||||
class="inline-block px-2 py-0.5 text-xs text-gray-600 bg-gray-100 rounded-full cursor-pointer hover:bg-yellow-50 hover:text-yellow-700 transition"
|
||||
>
|
||||
普通用户
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divder h-1px bg-gray-200 my-4px" />
|
||||
|
||||
<div v-for="item in popoverList" :key="item.key" class="popover-content-box-items h-full">
|
||||
<div
|
||||
v-if="!item.divider"
|
||||
class="popover-content-box-item flex items-center h-full gap-8px p-8px pl-10px pr-12px rounded-lg hover:cursor-pointer hover:bg-[rgba(0,0,0,.04)]"
|
||||
@click="handleClick(item)"
|
||||
>
|
||||
<SvgIcon :name="item.icon!" size="16" class-name="flex-none" />
|
||||
<div class="popover-content-box-item-text font-size-14px text-overflow max-h-120px">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="item.divider" class="divder h-1px bg-gray-200 my-4px" />
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
<nav-dialog
|
||||
v-model="dialogVisible"
|
||||
title="控制台"
|
||||
:nav-items="navItems"
|
||||
:default-active="activeNav"
|
||||
@confirm="handleConfirm"
|
||||
@nav-change="handleNavChange"
|
||||
>
|
||||
<template #extra-actions>
|
||||
<el-tooltip v-if="isUserVip() && activeNav === 'rechargeLog'" content="联系售后" placement="bottom">
|
||||
<el-button circle plain size="small" @click="handleContactSupport">
|
||||
<el-icon color="#07c160">
|
||||
<ChatLineRound />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
|
||||
<!-- 用户管理内容 -->
|
||||
<template #user>
|
||||
<user-management />
|
||||
</template>
|
||||
<!-- 用量统计 -->
|
||||
<template #usageStatistics>
|
||||
<usage-statistics />
|
||||
</template>
|
||||
<!-- 尊享服务 -->
|
||||
<template #premiumService>
|
||||
<premium-service />
|
||||
</template>
|
||||
<!-- 用量统计 -->
|
||||
<!-- <template #usageStatistics2> -->
|
||||
<!-- <usage-statistics2 /> -->
|
||||
<!-- </template> -->
|
||||
|
||||
<!-- 角色管理内容 -->
|
||||
<template #role>
|
||||
<!-- < /> -->
|
||||
</template>
|
||||
|
||||
<!-- 权限管理内容 -->
|
||||
<template #permission>
|
||||
<!-- <permission-management /> -->
|
||||
</template>
|
||||
|
||||
<template #apiKey>
|
||||
<APIKeyManagement />
|
||||
</template>
|
||||
<template #activationCode>
|
||||
<activation-code />
|
||||
</template>
|
||||
<template #dailyTask>
|
||||
<daily-task />
|
||||
</template>
|
||||
<template #cardFlip>
|
||||
<card-flip-activity :external-invite-code="externalInviteCode" />
|
||||
</template>
|
||||
<template #rechargeLog>
|
||||
<recharge-log ref="rechargeLogRef" />
|
||||
</template>
|
||||
</nav-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.popover-content {
|
||||
width: 520px;
|
||||
height: 520px;
|
||||
}
|
||||
.popover-content-box {
|
||||
padding: 8px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgb(0 0 0 / 8%);
|
||||
}
|
||||
</style>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,39 @@
|
||||
<!-- 侧边栏折叠按钮 -->
|
||||
<script setup lang="ts">
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
import { SIDE_BAR_WIDTH } from '@/config/index';
|
||||
import { useCollapseToggle } from '@/hooks/useCollapseToggle';
|
||||
import { useDesignStore } from '@/stores';
|
||||
|
||||
const { changeCollapse } = useCollapseToggle();
|
||||
const designStore = useDesignStore();
|
||||
|
||||
function handleChangeCollapse() {
|
||||
changeCollapse();
|
||||
// 每次切换折叠状态,重置安全区状态
|
||||
designStore.isSafeAreaHover = false;
|
||||
// 重置首次激活悬停状态
|
||||
designStore.hasActivatedHover = false;
|
||||
if (!designStore.isCollapse) {
|
||||
document.documentElement.style.setProperty(
|
||||
`--sidebar-left-container-default-width`,
|
||||
`${SIDE_BAR_WIDTH}px`,
|
||||
);
|
||||
}
|
||||
else {
|
||||
document.documentElement.style.setProperty(`--sidebar-left-container-default-width`, ``);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="collapse-container btn-icon-btn" @click="handleChangeCollapse">
|
||||
<SvgIcon v-if="!designStore.isCollapse" name="ms-left-panel-close-outline" size="24" />
|
||||
<SvgIcon v-if="designStore.isCollapse" name="ms-left-panel-open-outline" size="24" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
// .collapse-container {
|
||||
// }
|
||||
</style>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- 添加新会话按钮 -->
|
||||
<script setup lang="ts">
|
||||
import { useSessionStore } from '@/stores/modules/session';
|
||||
|
||||
const sessionStore = useSessionStore();
|
||||
|
||||
/* 创建会话 开始 */
|
||||
function handleCreatChat() {
|
||||
if (!sessionStore.currentSession)
|
||||
return;
|
||||
// 创建会话, 跳转到默认聊天
|
||||
sessionStore.createSessionBtn();
|
||||
}
|
||||
/* 创建会话 结束 */
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="create-chat-container flex-center flex-none p-6px pl-8px pr-8px c-#0057ff b-#0057ff b-rounded-12px border-1px hover:bg-#0057ff hover:c-#fff hover:b-#fff hover:cursor-pointer border-solid select-none"
|
||||
:class="{
|
||||
'is-disabled': !sessionStore.currentSession,
|
||||
}"
|
||||
@click="handleCreatChat"
|
||||
>
|
||||
<el-icon size="12" class="flex-center flex-none w-14px h-14px">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
<span class="ml-4px font-size-14px font-700">新对话</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.is-disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
&:hover {
|
||||
color: #0057ff;
|
||||
cursor: not-allowed;
|
||||
background-color: transparent;
|
||||
border-color: #0057ff;
|
||||
border-style: solid;
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,29 @@
|
||||
<!-- LoginBtn 登录按钮 -->
|
||||
<script setup lang="ts">
|
||||
import LoginDialog from '@/components/LoginDialog/index.vue';
|
||||
import { useUserStore } from '@/stores';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const isLoginDialogVisible = computed(() => userStore.isLoginDialogVisible);
|
||||
|
||||
// 点击登录按钮时调用Store方法打开弹框
|
||||
function handleClickLogin() {
|
||||
userStore.openLoginDialog();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-btn-wrapper">
|
||||
<div
|
||||
class="login-btn bg-#191c1f c-#fff font-size-14px rounded-8px flex-center text-overflow p-10px pl-12px pr-12px min-w-49px h-16px cursor-pointer hover:bg-#232629 select-none"
|
||||
@click="handleClickLogin"
|
||||
>
|
||||
登录
|
||||
</div>
|
||||
|
||||
<!-- 登录弹框 -->
|
||||
<LoginDialog v-model:visible="isLoginDialogVisible" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
@@ -0,0 +1,88 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
function goToModelLibrary() {
|
||||
router.push('/model-library');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="model-library-btn-container" data-tour="model-library-btn">
|
||||
<div
|
||||
class="model-library-btn"
|
||||
title="查看模型库"
|
||||
@click="goToModelLibrary"
|
||||
>
|
||||
<!-- 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="3" y="3" width="7" height="7" />
|
||||
<rect x="14" y="3" width="7" height="7" />
|
||||
<rect x="14" y="14" width="7" height="7" />
|
||||
<rect x="3" y="14" width="7" height="7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.model-library-btn-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.model-library-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: #606266;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
// PC端显示文字,隐藏图标
|
||||
.pc-text {
|
||||
display: inline;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.mobile-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端显示图标,隐藏文字
|
||||
@media (max-width: 768px) {
|
||||
.model-library-btn-container {
|
||||
.model-library-btn {
|
||||
.pc-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-icon {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<script setup lang="ts">
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
// 检查是否在聊天页面
|
||||
const isOnChatPage = computed(() => {
|
||||
return route.path.startsWith('/chat');
|
||||
});
|
||||
|
||||
function goToChat() {
|
||||
router.push('/chat/conversation');
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="!isOnChatPage" class="start-chat-btn-container" data-tour="start-chat-btn">
|
||||
<div
|
||||
class="start-chat-btn"
|
||||
title="开始聊天"
|
||||
@click="goToChat"
|
||||
>
|
||||
<el-icon class="chat-icon">
|
||||
<i-ep-chat-dot-round />
|
||||
</el-icon>
|
||||
<span class="btn-text">开始聊天</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.start-chat-btn-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 12px;
|
||||
|
||||
.start-chat-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
border-radius: 8px;
|
||||
background: linear-gradient(135deg, var(--el-color-primary) 0%, var(--el-color-primary-light-3) 100%);
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
transition: all 0.3s;
|
||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.chat-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.btn-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端隐藏文字
|
||||
@media (max-width: 768px) {
|
||||
.start-chat-btn-container {
|
||||
margin-right: 8px;
|
||||
|
||||
.start-chat-btn {
|
||||
padding: 8px;
|
||||
|
||||
.btn-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,95 @@
|
||||
<script setup lang="ts">
|
||||
import { useColorMode } from '@vueuse/core';
|
||||
|
||||
// 使用 VueUse 的 useColorMode
|
||||
const mode = useColorMode({
|
||||
attribute: 'class',
|
||||
modes: {
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
},
|
||||
});
|
||||
|
||||
// 切换主题
|
||||
function toggleTheme() {
|
||||
mode.value = mode.value === 'dark' ? 'light' : 'dark';
|
||||
}
|
||||
|
||||
// 主题图标
|
||||
const themeIcon = computed(() => {
|
||||
return mode.value === 'dark' ? 'Sunny' : 'Moon';
|
||||
});
|
||||
|
||||
// 主题标题
|
||||
const themeTitle = computed(() => {
|
||||
return mode.value === 'dark' ? '切换到浅色模式' : '切换到深色模式';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="theme-btn-container" data-tour="theme-btn">
|
||||
<div
|
||||
class="theme-btn"
|
||||
:title="themeTitle"
|
||||
@click="toggleTheme"
|
||||
>
|
||||
<!-- PC端显示文字 + 图标 -->
|
||||
<el-icon class="theme-icon">
|
||||
<component :is="`i-ep-${themeIcon}`" />
|
||||
</el-icon>
|
||||
<span class="pc-text">主题</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.theme-btn-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.theme-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s;
|
||||
color: var(--el-text-color-regular);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--el-fill-color-light);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.theme-icon {
|
||||
font-size: 18px;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
&:hover .theme-icon {
|
||||
transform: rotate(20deg);
|
||||
}
|
||||
|
||||
// PC端显示文字
|
||||
.pc-text {
|
||||
display: inline;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端隐藏文字
|
||||
@media (max-width: 768px) {
|
||||
.theme-btn-container {
|
||||
.theme-btn {
|
||||
padding: 8px;
|
||||
|
||||
.pc-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,87 @@
|
||||
<!-- 标题编辑 -->
|
||||
<script setup lang="ts">
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue';
|
||||
import { useSessionStore } from '@/stores/modules/session';
|
||||
|
||||
const sessionStore = useSessionStore();
|
||||
|
||||
const currentSession = computed(() => sessionStore.currentSession);
|
||||
|
||||
function handleClickTitle() {
|
||||
ElMessageBox.prompt('', '编辑对话名称', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
inputErrorMessage: '请输入对话名称',
|
||||
confirmButtonClass: 'el-button--primary',
|
||||
cancelButtonClass: 'el-button--info',
|
||||
roundButton: true,
|
||||
inputValue: currentSession.value?.sessionTitle,
|
||||
inputValidator: (value) => {
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
})
|
||||
.then(({ value }) => {
|
||||
sessionStore
|
||||
.updateSession({
|
||||
id: currentSession.value!.id,
|
||||
sessionTitle: value,
|
||||
sessionContent: currentSession.value!.sessionContent,
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '修改成功',
|
||||
});
|
||||
nextTick(() => {
|
||||
// 如果是当前会话,则更新当前选中会话信息
|
||||
sessionStore.setCurrentSession({
|
||||
...currentSession.value,
|
||||
sessionTitle: value,
|
||||
});
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// ElMessage({
|
||||
// type: 'info',
|
||||
// message: '取消修改',
|
||||
// });
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="currentSession" class="w-full h-full flex flex-col justify-center">
|
||||
<div class="box-border mr-20px">
|
||||
<div
|
||||
class="title-editing-container p-4px w-fit max-w-full flex items-center justify-start cursor-pointer select-none hover:bg-[rgba(0,0,0,.04)] cursor-pointer rounded-md font-size-14px"
|
||||
@click="handleClickTitle"
|
||||
>
|
||||
<div class="text-overflow select-none pr-8px">
|
||||
{{ currentSession.sessionTitle }}
|
||||
</div>
|
||||
<SvgIcon name="draft-line" size="14" class="flex-none c-gray-500" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.title-editing-container {
|
||||
transition: all 0.3s ease;
|
||||
&:hover {
|
||||
.svg-icon {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.svg-icon {
|
||||
display: none;
|
||||
opacity: 0.5;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,75 @@
|
||||
<script setup lang="ts">
|
||||
import { QuestionFilled } from '@element-plus/icons-vue';
|
||||
import { useGuideTour } from '@/hooks/useGuideTour';
|
||||
|
||||
const { startHeaderTour } = useGuideTour();
|
||||
|
||||
// 开始引导教程
|
||||
function handleStartTutorial() {
|
||||
startHeaderTour();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="tutorial-btn-container" data-tour="tutorial-btn">
|
||||
<div
|
||||
class="tutorial-btn"
|
||||
@click="handleStartTutorial"
|
||||
>
|
||||
<!-- PC端显示文字 -->
|
||||
<span class="pc-text">新手引导</span>
|
||||
<!-- 移动端显示图标 -->
|
||||
<el-icon class="mobile-icon" :size="20">
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tutorial-btn-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.tutorial-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
color: #409eff;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #66b1ff;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
// PC端显示文字,隐藏图标
|
||||
.pc-text {
|
||||
display: inline;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.mobile-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 移动端显示图标,隐藏文字
|
||||
@media (max-width: 768px) {
|
||||
.tutorial-btn-container {
|
||||
.tutorial-btn {
|
||||
.pc-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile-icon {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user