deotalandAi/apps/frontend/src/components/GuideModal/index.vue

473 lines
10 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div v-if="show" class="guide-modal-overlay" @click="handleOverlayClick">
<div class="guide-modal-container" @click.stop>
<!-- 关闭按钮 -->
<button class="close-button" @click="closeModal" :title="t('header.skipGuide')">
<span class="close-icon">×</span>
</button>
<!-- 进度指示器 -->
<div class="progress-indicator">
<div
v-for="(step, index) in guideSteps"
:key="index"
class="progress-dot"
:class="{ active: index === currentStep, completed: index < currentStep }"
></div>
</div>
<!-- 引导内容区域 -->
<div class="guide-content">
<!-- 添加轮播容器 -->
<div class="guide-content-wrapper" :style="{ transform: `translateX(-${currentStep * 100}%)` }">
<div v-for="(step, index) in guideSteps" :key="index" class="guide-step">
<!-- 左侧图片区域 -->
<div class="guide-image-container">
<div class="image-wrapper">
<img
:src="step.image"
:alt="step.title"
class="guide-image"
/>
<div class="image-decoration"></div>
</div>
</div>
<!-- 右侧文字描述区域 -->
<div class="guide-text-container">
<div class="text-content">
<h2 class="guide-title">{{ step.title }}</h2>
<p class="guide-description">{{ step.description }}</p>
<!-- 额外提示信息 -->
<div v-if="step.tips" class="guide-tips">
<div class="tips-icon">💡</div>
<div class="tips-text">{{ step.tips }}</div>
</div>
</div>
<!-- 按钮区域 -->
<div class="guide-actions">
<!-- 上一步按钮 -->
<button
v-if="currentStep > 0"
class="action-button secondary"
@click="prevStep"
>
{{ t('header.previous') }}
</button>
<!-- 跳过按钮 -->
<button
class="action-button skip"
@click="skipGuide"
>
{{ t('header.skipGuide') }}
</button>
<!-- 下一步/完成按钮 -->
<button
class="action-button primary"
@click="nextStep"
>
{{ isLastStep ? t('header.startCreating') : t('header.next') }}
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 步骤指示器 -->
<div class="step-indicator">
<span class="step-text">{{ t('header.step') }} {{ currentStep + 1 }} / {{ guideSteps.length }}</span>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n';
// 定义props
const props = defineProps({
show: {
type: Boolean,
default: false
}
});
// 定义emits
const emit = defineEmits(['close', 'complete']);
// 国际化
const { t } = useI18n();
// 当前步骤索引
const currentStep = ref(0);
// 引导步骤数据
const guideSteps = computed(() => [
{
id: 1,
title: t('guideModal.step1.title'),
description: t('guideModal.step1.description'),
image: new URL('@/assets/step/creatProject/step1.png', import.meta.url).href,
tips: t('guideModal.step1.tips')
},
{
id: 2,
title: t('guideModal.step2.title'),
description: t('guideModal.step2.description'),
image: new URL('@/assets/step/creatProject/step2.png', import.meta.url).href,
tips: t('guideModal.step2.tips')
},
{
id: 3,
title: t('guideModal.step3.title'),
description: t('guideModal.step3.description'),
image: new URL('@/assets/step/creatProject/step3.png', import.meta.url).href,
tips: t('guideModal.step3.tips')
},
{
id: 4,
title: t('guideModal.step4.title'),
description: t('guideModal.step4.description'),
image: new URL('@/assets/step/creatProject/step4.png', import.meta.url).href,
tips: t('guideModal.step4.tips')
}
]);
// 计算属性:当前步骤数据
const currentStepData = computed(() => {
return guideSteps.value[currentStep.value] || {};
});
// 计算属性:是否为最后一步
const isLastStep = computed(() => {
return currentStep.value === guideSteps.value.length - 1;
});
// 方法:上一步
const prevStep = () => {
if (currentStep.value > 0) {
currentStep.value--;
}
};
// 方法:下一步
const nextStep = () => {
if (currentStep.value < guideSteps.value.length - 1) {
currentStep.value++;
} else {
// 如果是最后一步,完成引导
completeGuide();
}
};
// 方法:关闭弹窗
const closeModal = () => {
emit('close');
};
// 方法:完成引导
const completeGuide = () => {
emit('complete');
closeModal();
};
// 方法:跳过引导
const skipGuide = () => {
closeModal();
};
</script>
<style scoped>
.guide-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
backdrop-filter: blur(3px);
animation: fadeIn 0.3s ease-out;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.guide-modal-container {
background: linear-gradient(135deg, rgba(107, 70, 193, 0.85) 0%, rgba(147, 51, 234, 0.85) 100%);
backdrop-filter: blur(10px);
border-radius: 20px;
width: 90%;
max-width: 900px;
max-height: 85vh;
overflow: hidden;
position: relative;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
animation: slideUp 0.4s ease-out;
color: white;
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(30px); }
to { opacity: 1; transform: translateY(0); }
}
.close-button {
position: absolute;
top: 20px;
right: 20px;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.2);
color: white;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
z-index: 10;
}
.close-button:hover {
background-color: rgba(255, 255, 255, 0.25);
transform: scale(1.1);
}
.close-icon {
font-size: 24px;
line-height: 1;
font-weight: 300;
}
.progress-indicator {
display: flex;
justify-content: center;
padding: 20px 0 10px;
gap: 8px;
}
.progress-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.3);
transition: all 0.3s ease;
}
.progress-dot.active {
width: 24px;
border-radius: 4px;
background-color: white;
}
.progress-dot.completed {
background-color: rgba(255, 255, 255, 0.7);
}
.guide-content {
display: flex;
flex: 1;
overflow: hidden;
position: relative;
}
/* 添加轮播容器 */
.guide-content-wrapper {
display: flex;
width: 100%;
height: 100%;
transition: transform 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
.guide-step {
min-width: 100%;
display: flex;
flex: 1;
}
.guide-image-container {
flex: 1;
padding: 0 20px 20px 40px;
display: flex;
align-items: center;
justify-content: center;
}
.image-wrapper {
position: relative;
width: 100%;
height: 100%;
max-height: 350px;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.guide-image {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.5s ease;
}
.image-wrapper:hover .guide-image {
transform: scale(1.03);
}
.image-decoration {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(45deg, rgba(107, 70, 193, 0.2) 0%, rgba(147, 51, 234, 0.2) 100%);
}
.guide-text-container {
flex: 1;
padding: 0 40px 20px 20px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.text-content {
flex: 1;
}
.guide-title {
font-size: 28px;
font-weight: 700;
margin: 0 0 16px;
line-height: 1.2;
}
.guide-description {
font-size: 16px;
line-height: 1.6;
margin: 0 0 20px;
opacity: 0.9;
}
.guide-tips {
display: flex;
align-items: flex-start;
background-color: rgba(255, 255, 255, 0.15);
border-radius: 12px;
padding: 12px;
margin-top: 16px;
}
.tips-icon {
font-size: 18px;
margin-right: 8px;
flex-shrink: 0;
}
.tips-text {
font-size: 14px;
line-height: 1.4;
}
.guide-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
.action-button {
padding: 12px 24px;
border-radius: 8px;
font-weight: 600;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
border: none;
display: flex;
align-items: center;
justify-content: center;
}
.action-button.primary {
background-color: rgba(255, 255, 255, 0.9);
color: #6B46C1;
}
.action-button.primary:hover {
background-color: rgba(255, 255, 255, 0.95);
transform: translateY(-2px);
}
.action-button.secondary {
background-color: rgba(255, 255, 255, 0.15);
color: white;
border: 1px solid rgba(255, 255, 255, 0.3);
}
.action-button.secondary:hover {
background-color: rgba(255, 255, 255, 0.25);
}
.action-button.skip {
background-color: transparent;
color: rgba(255, 255, 255, 0.7);
margin-right: auto;
}
.action-button.skip:hover {
color: white;
text-decoration: underline;
}
.step-indicator {
text-align: center;
padding: 16px 0;
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.step-text {
font-size: 14px;
opacity: 0.7;
}
/* 响应式设计 */
@media (max-width: 768px) {
.guide-step {
flex-direction: column;
}
.guide-image-container,
.guide-text-container {
padding: 0 20px 20px;
}
.guide-title {
font-size: 24px;
}
.guide-actions {
flex-wrap: wrap;
}
.action-button.skip {
margin-right: 0;
margin-bottom: 8px;
}
}
</style>