feat: 添加 SpotlightOverlay 组件,更新引导步骤组件以支持高亮功能,调整样式和路由配置

This commit is contained in:
zhongyidian committed 2025-09-16 01:26:27 +08:00
1 parent aeec7fceed
commit 2b36342304
36 files changed
+863 -29

No files matched your search

+98 -28
View File
@@ -1,33 +1,44 @@
<template>
<div class="guide-steps-container" v-if="show">
<GuideDragon
:show-skip="currentStep < totalSteps"
:current-step="currentStep"
:total-steps="totalSteps"
@next="handleNext"
@skip="handleSkip"
@complete="handleComplete"
>
<template #default>
<!-- 支持插槽形式的步骤内容 -->
<div v-if="useSlotMode">
<component
:is="currentStepComponent"
@continue="handleNext"
:is-last-step="currentStep >= totalSteps"
:current-step="currentStep"
:total-steps="totalSteps"
/>
</div>
<!-- 兼容原有的配置形式 -->
<div v-else v-html="currentStepContent"></div>
</template>
</GuideDragon>
</div>
<teleport to="body">
<div class="guide-steps-container" v-if="show">
<SpotlightOverlay
:visible="Boolean(highlightSelector)"
:target-selector="highlightSelector"
:padding="highlightPadding"
:border-radius="highlightRadius"
/>
<div class="guide-align" :style="alignStyle">
<GuideDragon
:show-skip="currentStep < totalSteps"
:current-step="currentStep"
:total-steps="totalSteps"
@next="handleNext"
@skip="handleSkip"
@complete="handleComplete"
>
<template #default>
<!-- 支持插槽形式的步骤内容 -->
<div v-if="useSlotMode">
<component
:is="currentStepComponent"
@continue="handleNext"
:is-last-step="currentStep >= totalSteps"
:current-step="currentStep"
:total-steps="totalSteps"
/>
</div>
<!-- 兼容原有的配置形式 -->
<div v-else v-html="currentStepContent"></div>
</template>
</GuideDragon>
</div>
</div>
</teleport>
</template>
<script setup lang="ts">
import { ref, computed, useSlots, onMounted, onUnmounted } from 'vue'
import { ref, computed, useSlots, onMounted, onUnmounted, reactive } from 'vue'
import SpotlightOverlay from './SpotlightOverlay.vue'
import GuideDragon from './GuideDragon.vue'
interface GuideStep {
@@ -46,6 +57,12 @@ interface Props {
keyboardNavigation?: boolean
/** 自动播放间隔时间(毫秒),0表示不自动播放 */
autoPlayInterval?: number
/** 当前步骤高亮目标的选择器(CSS选择器),为空则不高亮 */
highlightSelector?: string | null
/** 高亮内边距,默认8 */
highlightPadding?: number
/** 高亮圆角,默认16 */
highlightRadius?: number
}
interface Emits {
@@ -58,7 +75,10 @@ const props = withDefaults(defineProps<Props>(), {
steps: () => [],
autoStart: true,
keyboardNavigation: true,
autoPlayInterval: 0
autoPlayInterval: 0,
highlightSelector: null,
highlightPadding: 8,
highlightRadius: 16
})
const emit = defineEmits<Emits>()
@@ -66,6 +86,22 @@ const emit = defineEmits<Emits>()
const slots = useSlots()
const show = ref(true)
const currentStepIndex = ref(0)
const alignStyle = reactive<{
left: string
top: string
transform: string
width: string
height: string
}>({
left: '0px',
top: '0px',
transform: 'scale(1)',
width: '1920px',
height: '1080px'
})
const highlightSelector = computed(() => props.highlightSelector || null)
const highlightPadding = computed(() => props.highlightPadding || 0)
const highlightRadius = computed(() => props.highlightRadius || 0)
const useSlotMode = computed(() => {
return slots.default && slots.default().length > 0
@@ -209,11 +245,34 @@ onMounted(() => {
if (props.autoStart && props.autoPlayInterval > 0) {
startAutoPlay()
}
// 同步到 .app-content 的缩放与偏移,使引导面板与页面对齐
const syncAlign = () => {
const el = document.querySelector('.app-content') as HTMLElement | null
if (!el) return
const style = window.getComputedStyle(el)
const transform = style.transform
let scale = 1
if (transform && transform !== 'none') {
const match = transform.match(/matrix\(([^)]+)\)/)
if (match) {
const parts = match[1].split(',').map(s => parseFloat(s.trim()))
if (!Number.isNaN(parts[0])) scale = parts[0]
}
}
alignStyle.transform = `scale(${scale})`
alignStyle.left = style.left || '0px'
alignStyle.top = style.top || '0px'
alignStyle.width = style.width || '1920px'
alignStyle.height = style.height || '1080px'
}
syncAlign()
window.addEventListener('resize', syncAlign)
})
onUnmounted(() => {
document.removeEventListener('keydown', handleKeydown)
stopAutoPlay()
window.removeEventListener('resize', () => {})
})
defineExpose({
@@ -228,6 +287,17 @@ defineExpose({
<style scoped>
.guide-steps-container {
@apply w-full h-full relative;
@apply w-full h-full;
position: fixed;
inset: 0;
z-index: 10001;
pointer-events: none;
}
.guide-align {
position: absolute;
transform-origin: top left;
pointer-events: none;
z-index: 100002;
}
</style>