feat: 新手引导优化
This commit is contained in:
@@ -7,6 +7,7 @@ import { getQrCode, getQrCodeResult, getUserInfo } from '@/api';
|
|||||||
import { useUserStore } from '@/stores';
|
import { useUserStore } from '@/stores';
|
||||||
import { useSessionStore } from '@/stores/modules/session.ts';
|
import { useSessionStore } from '@/stores/modules/session.ts';
|
||||||
import { WECHAT_QRCODE_TYPE } from '@/utils/user.ts';
|
import { WECHAT_QRCODE_TYPE } from '@/utils/user.ts';
|
||||||
|
import { useGuideTour } from '@/hooks/useGuideTour';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
type: {
|
type: {
|
||||||
@@ -29,6 +30,7 @@ const userStore = useUserStore();
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const sessionStore = useSessionStore();
|
const sessionStore = useSessionStore();
|
||||||
const isQrCodeError = ref(false);
|
const isQrCodeError = ref(false);
|
||||||
|
const { startFullTour } = useGuideTour();
|
||||||
// 二维码倒计时实例
|
// 二维码倒计时实例
|
||||||
const { start: qrStart, stop: qrStop } = useCountdown(shallowRef(600), {
|
const { start: qrStart, stop: qrStop } = useCountdown(shallowRef(600), {
|
||||||
interval: 1000,
|
interval: 1000,
|
||||||
@@ -126,6 +128,11 @@ async function handleLoginSuccess(token: string, refreshToken: string) {
|
|||||||
stopPolling();
|
stopPolling();
|
||||||
userStore.setToken(token, refreshToken);
|
userStore.setToken(token, refreshToken);
|
||||||
const resUserInfo = await getUserInfo();
|
const resUserInfo = await getUserInfo();
|
||||||
|
|
||||||
|
// 判断是否为新用户(注册时间小于1小时)
|
||||||
|
const creationTime = resUserInfo.data.user.creationTime; // 格式: "2024-11-01 12:01:34"
|
||||||
|
const isNewUser = checkIsNewUser(creationTime);
|
||||||
|
|
||||||
userStore.setUserInfo(resUserInfo.data);
|
userStore.setUserInfo(resUserInfo.data);
|
||||||
// 提示用户
|
// 提示用户
|
||||||
ElMessage.success('登录成功');
|
ElMessage.success('登录成功');
|
||||||
@@ -133,6 +140,34 @@ async function handleLoginSuccess(token: string, refreshToken: string) {
|
|||||||
await router.replace('/');
|
await router.replace('/');
|
||||||
await sessionStore.requestSessionList(1, true);
|
await sessionStore.requestSessionList(1, true);
|
||||||
userStore.closeLoginDialog();
|
userStore.closeLoginDialog();
|
||||||
|
|
||||||
|
// 如果是新用户,延迟500ms后自动触发新手引导
|
||||||
|
if (isNewUser) {
|
||||||
|
setTimeout(() => {
|
||||||
|
startFullTour();
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否为新用户(注册时间距离当前时间小于1小时)
|
||||||
|
function checkIsNewUser(creationTimeStr: string): boolean {
|
||||||
|
try {
|
||||||
|
// 解析注册时间字符串 "2024-11-01 12:01:34"
|
||||||
|
const creationTime = new Date(creationTimeStr.replace(' ', 'T'));
|
||||||
|
const currentTime = new Date();
|
||||||
|
|
||||||
|
// 计算时间差(毫秒)
|
||||||
|
const timeDiff = currentTime.getTime() - creationTime.getTime();
|
||||||
|
|
||||||
|
// 1小时 = 60分钟 * 60秒 * 1000毫秒 = 3600000毫秒
|
||||||
|
const oneHourInMs = 60 * 60 * 1000;
|
||||||
|
|
||||||
|
return timeDiff < oneHourInMs;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error('解析注册时间失败:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理注册授权
|
// 处理注册授权
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { driver } from 'driver.js';
|
import { driver } from 'driver.js';
|
||||||
import 'driver.js/dist/driver.css';
|
|
||||||
import { useGuideTourStore } from '@/stores';
|
import { useGuideTourStore } from '@/stores';
|
||||||
|
import 'driver.js/dist/driver.css';
|
||||||
|
|
||||||
// 引导步骤接口
|
// 引导步骤接口
|
||||||
interface TourStep {
|
interface TourStep {
|
||||||
@@ -47,7 +47,7 @@ export function useGuideTour() {
|
|||||||
element: '[data-tour="tutorial-btn"]',
|
element: '[data-tour="tutorial-btn"]',
|
||||||
popover: {
|
popover: {
|
||||||
title: '新手教程',
|
title: '新手教程',
|
||||||
description: '欢迎使用YiXinAI!点击这里可以随时重新查看新手引导教程,帮助您快速了解系统功能。',
|
description: '欢迎使用意心AI-YiXinAI!点击这里可以随时重新查看新手引导教程,帮助您快速了解系统功能。',
|
||||||
side: 'bottom',
|
side: 'bottom',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
@@ -56,7 +56,7 @@ export function useGuideTour() {
|
|||||||
element: '[data-tour="model-select"]',
|
element: '[data-tour="model-select"]',
|
||||||
popover: {
|
popover: {
|
||||||
title: '模型选择',
|
title: '模型选择',
|
||||||
description: '点击这里可以切换不同的AI模型,每个模型有不同的特点和能力。VIP用户可以使用所有高级模型。',
|
description: '点击这里可以切换不同的AI模型,每个模型有不同的特点和能力。VIP用户可以更多高级模型。',
|
||||||
side: 'top',
|
side: 'top',
|
||||||
align: 'start',
|
align: 'start',
|
||||||
},
|
},
|
||||||
@@ -65,7 +65,7 @@ export function useGuideTour() {
|
|||||||
element: '[data-tour="chat-sender"]',
|
element: '[data-tour="chat-sender"]',
|
||||||
popover: {
|
popover: {
|
||||||
title: '对话输入框',
|
title: '对话输入框',
|
||||||
description: '在这里输入您的问题或指令,按回车或点击发送按钮即可与AI对话。支持语音输入和文件上传。',
|
description: '在这里输入您的问题或指令,按回车或点击发送按钮即可与AI对话。支持语音输入',
|
||||||
side: 'top',
|
side: 'top',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
@@ -92,7 +92,7 @@ export function useGuideTour() {
|
|||||||
element: '[data-tour="buy-btn"]',
|
element: '[data-tour="buy-btn"]',
|
||||||
popover: {
|
popover: {
|
||||||
title: '立即购买',
|
title: '立即购买',
|
||||||
description: '点击这里可以购买Token套餐或升级VIP会员,享受更多专属服务和权益。',
|
description: '点击这里可以成为VIP会员和升级尊享服务,享受更多专属服务和权益。',
|
||||||
side: 'bottom',
|
side: 'bottom',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
@@ -123,7 +123,7 @@ export function useGuideTour() {
|
|||||||
element: '[data-tour="nav-user"]',
|
element: '[data-tour="nav-user"]',
|
||||||
popover: {
|
popover: {
|
||||||
title: '用户信息',
|
title: '用户信息',
|
||||||
description: '查看和修改您的个人信息,包括头像、昵称等。',
|
description: '查看您的个人信息,包括头像、昵称等。',
|
||||||
side: 'right',
|
side: 'right',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
@@ -171,7 +171,7 @@ export function useGuideTour() {
|
|||||||
element: '[data-tour="nav-premiumService"]',
|
element: '[data-tour="nav-premiumService"]',
|
||||||
popover: {
|
popover: {
|
||||||
title: '尊享服务',
|
title: '尊享服务',
|
||||||
description: '了解VIP会员专属特权和服务,我们将详细介绍这个页面的功能。',
|
description: '了解尊享服务包专属特权和服务,我们将详细介绍这个页面的功能。',
|
||||||
side: 'right',
|
side: 'right',
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
@@ -250,7 +250,6 @@ export function useGuideTour() {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
// 开始 Header 引导
|
// 开始 Header 引导
|
||||||
async function startHeaderTour() {
|
async function startHeaderTour() {
|
||||||
if (!driverInstance.value) {
|
if (!driverInstance.value) {
|
||||||
|
|||||||
@@ -32,7 +32,10 @@ function openAnnouncement() {
|
|||||||
class="announcement-btn"
|
class="announcement-btn"
|
||||||
@click="openAnnouncement"
|
@click="openAnnouncement"
|
||||||
>
|
>
|
||||||
<el-icon :size="20">
|
<!-- PC端显示文字 -->
|
||||||
|
<span class="pc-text">公告/活动</span>
|
||||||
|
<!-- 移动端显示图标 -->
|
||||||
|
<el-icon class="mobile-icon" :size="20">
|
||||||
<Bell />
|
<Bell />
|
||||||
</el-icon>
|
</el-icon>
|
||||||
</div>
|
</div>
|
||||||
@@ -44,65 +47,52 @@ function openAnnouncement() {
|
|||||||
.announcement-btn-container {
|
.announcement-btn-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-right: 12px;
|
|
||||||
|
|
||||||
.announcement-badge {
|
.announcement-badge {
|
||||||
:deep(.el-badge__content) {
|
:deep(.el-badge__content) {
|
||||||
background-color: #f56c6c;
|
background-color: #f56c6c;
|
||||||
border: none;
|
border: none;
|
||||||
//font-size: 12px;
|
|
||||||
//height: 18px;
|
|
||||||
//line-height: 18px;
|
|
||||||
//padding: 0 6px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.announcement-btn {
|
.announcement-btn {
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 6px;
|
||||||
border-radius: 50%;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
.el-icon {
|
color: #e6a23c;
|
||||||
color: #606266;
|
transition: all 0.2s;
|
||||||
transition: color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, 0.05);
|
color: #ebb563;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
.el-icon {
|
// PC端显示文字,隐藏图标
|
||||||
color: #409eff;
|
.pc-text {
|
||||||
}
|
display: inline;
|
||||||
|
margin: 0 12px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-icon {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移动端适配
|
// 移动端显示图标,隐藏文字
|
||||||
@media screen and (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.announcement-btn-container {
|
.announcement-btn-container {
|
||||||
margin-right: 8px;
|
|
||||||
|
|
||||||
.announcement-btn {
|
.announcement-btn {
|
||||||
width: 36px;
|
.pc-text {
|
||||||
height: 36px;
|
display: none;
|
||||||
|
|
||||||
.el-icon {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.announcement-badge {
|
.mobile-icon {
|
||||||
:deep(.el-badge__content) {
|
display: inline;
|
||||||
//font-size: 10px;
|
|
||||||
//height: 16px;
|
|
||||||
//line-height: 16px;
|
|
||||||
//padding: 0 4px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,16 +12,17 @@ function handleStartTutorial() {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="tutorial-btn-container" data-tour="tutorial-btn">
|
<div class="tutorial-btn-container" data-tour="tutorial-btn">
|
||||||
<el-tooltip content="新手教程" placement="bottom">
|
<div
|
||||||
<div
|
class="tutorial-btn"
|
||||||
class="tutorial-btn"
|
@click="handleStartTutorial"
|
||||||
@click="handleStartTutorial"
|
>
|
||||||
>
|
<!-- PC端显示文字 -->
|
||||||
<el-icon :size="20">
|
<span class="pc-text">新手引导</span>
|
||||||
<QuestionFilled />
|
<!-- 移动端显示图标 -->
|
||||||
</el-icon>
|
<el-icon class="mobile-icon" :size="20">
|
||||||
</div>
|
<QuestionFilled />
|
||||||
</el-tooltip>
|
</el-icon>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -29,45 +30,44 @@ function handleStartTutorial() {
|
|||||||
.tutorial-btn-container {
|
.tutorial-btn-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-right: 12px;
|
|
||||||
|
|
||||||
.tutorial-btn {
|
.tutorial-btn {
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
gap: 6px;
|
||||||
border-radius: 50%;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
font-size: 1.2rem;
|
||||||
|
font-weight: bold;
|
||||||
.el-icon {
|
color: #409eff;
|
||||||
color: #606266;
|
transition: all 0.2s;
|
||||||
transition: color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, 0.05);
|
color: #66b1ff;
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
.el-icon {
|
// PC端显示文字,隐藏图标
|
||||||
color: #409eff;
|
.pc-text {
|
||||||
}
|
display: inline;
|
||||||
|
margin: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-icon {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 移动端适配
|
// 移动端显示图标,隐藏文字
|
||||||
@media screen and (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.tutorial-btn-container {
|
.tutorial-btn-container {
|
||||||
margin-right: 8px;
|
|
||||||
|
|
||||||
.tutorial-btn {
|
.tutorial-btn {
|
||||||
width: 36px;
|
.pc-text {
|
||||||
height: 36px;
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.el-icon {
|
.mobile-icon {
|
||||||
font-size: 18px;
|
display: inline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,8 @@ onKeyStroke(event => event.ctrlKey && event.key.toLowerCase() === 'k', handleCtr
|
|||||||
|
|
||||||
<!-- 右边 -->
|
<!-- 右边 -->
|
||||||
<div class="right-box flex h-full items-center pr-20px flex-shrink-0 mr-auto flex-row">
|
<div class="right-box flex h-full items-center pr-20px flex-shrink-0 mr-auto flex-row">
|
||||||
<TutorialBtn />
|
|
||||||
<AnnouncementBtn />
|
<AnnouncementBtn />
|
||||||
|
<TutorialBtn />
|
||||||
<Avatar v-show="userStore.userInfo" />
|
<Avatar v-show="userStore.userInfo" />
|
||||||
<LoginBtn v-show="!userStore.userInfo" />
|
<LoginBtn v-show="!userStore.userInfo" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user