forked from zhongyidian/super_video
feat: 添加 Husky 和 commit 限制配置
This commit is contained in:
commit
27e0c23b3f
125 files changed
+5760
No files matched your search
@@ -0,0 +1,233 @@
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, useSlots, onMounted, onUnmounted } from 'vue'
|
||||
import GuideDragon from './GuideDragon.vue'
|
||||
|
||||
interface GuideStep {
|
||||
id: string
|
||||
title?: string
|
||||
content: string
|
||||
nextText?: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
/** 引导步骤配置 - 当使用插槽模式时可以为空 */
|
||||
steps?: GuideStep[]
|
||||
/** 是否自动开始 */
|
||||
autoStart?: boolean
|
||||
/** 是否启用键盘导航 */
|
||||
keyboardNavigation?: boolean
|
||||
/** 自动播放间隔时间(毫秒),0表示不自动播放 */
|
||||
autoPlayInterval?: number
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'complete'): void
|
||||
(e: 'skip'): void
|
||||
(e: 'step-change', step: GuideStep | null, index: number): void
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
steps: () => [],
|
||||
autoStart: true,
|
||||
keyboardNavigation: true,
|
||||
autoPlayInterval: 0
|
||||
})
|
||||
|
||||
const emit = defineEmits<Emits>()
|
||||
|
||||
const slots = useSlots()
|
||||
const show = ref(true)
|
||||
const currentStepIndex = ref(0)
|
||||
|
||||
const useSlotMode = computed(() => {
|
||||
return slots.default && slots.default().length > 0
|
||||
})
|
||||
|
||||
const slotSteps = computed(() => {
|
||||
if (!useSlotMode.value) return []
|
||||
return slots.default?.() || []
|
||||
})
|
||||
const currentStep = computed(() => currentStepIndex.value + 1)
|
||||
const totalSteps = computed(() => {
|
||||
return useSlotMode.value ? slotSteps.value.length : props.steps.length
|
||||
})
|
||||
|
||||
const currentStepData = computed(() => {
|
||||
if (useSlotMode.value) {
|
||||
return null // 插槽模式不需要step数据
|
||||
}
|
||||
if (props.steps.length === 0) {
|
||||
return null
|
||||
}
|
||||
return props.steps[currentStepIndex.value] || null
|
||||
})
|
||||
|
||||
const currentStepComponent = computed(() => {
|
||||
if (!useSlotMode.value) return null
|
||||
return slotSteps.value[currentStepIndex.value] || null
|
||||
})
|
||||
|
||||
const currentStepContent = computed(() => {
|
||||
if (useSlotMode.value) return ''
|
||||
|
||||
const step = currentStepData.value
|
||||
if (!step) return ''
|
||||
|
||||
if (!step.title) {
|
||||
return `<p>${step.content}</p>`
|
||||
}
|
||||
|
||||
return `
|
||||
<h3 class="text-lg font-bold mb-2">${step.title}</h3>
|
||||
<div class="leading-relaxed">${step.content}</div>
|
||||
`
|
||||
})
|
||||
|
||||
const handleNext = () => {
|
||||
const maxSteps = useSlotMode.value ? slotSteps.value.length : props.steps.length
|
||||
if (maxSteps === 0) {
|
||||
handleComplete()
|
||||
return
|
||||
}
|
||||
|
||||
if (currentStepIndex.value < maxSteps - 1) {
|
||||
currentStepIndex.value++
|
||||
emit('step-change', currentStepData.value, currentStepIndex.value)
|
||||
} else {
|
||||
handleComplete()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSkip = () => {
|
||||
handleComplete()
|
||||
}
|
||||
|
||||
const handleComplete = () => {
|
||||
show.value = false
|
||||
emit('complete')
|
||||
}
|
||||
|
||||
const goToStep = (index: number) => {
|
||||
const maxSteps = useSlotMode.value ? slotSteps.value.length : props.steps.length
|
||||
if (maxSteps === 0) {
|
||||
console.warn('GuideSteps goToStep: No steps available')
|
||||
return
|
||||
}
|
||||
|
||||
if (index >= 0 && index < maxSteps) {
|
||||
currentStepIndex.value = index
|
||||
emit('step-change', currentStepData.value, currentStepIndex.value)
|
||||
} else {
|
||||
console.warn('GuideSteps goToStep: Invalid step index', index, 'max:', maxSteps - 1)
|
||||
}
|
||||
}
|
||||
|
||||
const resetGuide = () => {
|
||||
currentStepIndex.value = 0
|
||||
show.value = true
|
||||
}
|
||||
|
||||
// 键盘导航
|
||||
const handleKeydown = (event: KeyboardEvent) => {
|
||||
if (!props.keyboardNavigation || !show.value) return
|
||||
|
||||
switch (event.key) {
|
||||
case 'ArrowRight':
|
||||
case ' ':
|
||||
event.preventDefault()
|
||||
handleNext()
|
||||
break
|
||||
case 'ArrowLeft':
|
||||
event.preventDefault()
|
||||
if (currentStepIndex.value > 0) {
|
||||
currentStepIndex.value--
|
||||
emit('step-change', currentStepData.value, currentStepIndex.value)
|
||||
}
|
||||
break
|
||||
case 'Escape':
|
||||
event.preventDefault()
|
||||
handleSkip()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// 自动播放
|
||||
let autoPlayTimer: number | null = null
|
||||
|
||||
const startAutoPlay = () => {
|
||||
if (props.autoPlayInterval > 0) {
|
||||
autoPlayTimer = window.setInterval(() => {
|
||||
const maxSteps = useSlotMode.value ? slotSteps.value.length : props.steps.length
|
||||
if (currentStepIndex.value < maxSteps - 1) {
|
||||
handleNext()
|
||||
} else {
|
||||
stopAutoPlay()
|
||||
handleComplete()
|
||||
}
|
||||
}, props.autoPlayInterval)
|
||||
}
|
||||
}
|
||||
|
||||
const stopAutoPlay = () => {
|
||||
if (autoPlayTimer) {
|
||||
clearInterval(autoPlayTimer)
|
||||
autoPlayTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
document.addEventListener('keydown', handleKeydown)
|
||||
if (props.autoStart && props.autoPlayInterval > 0) {
|
||||
startAutoPlay()
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
document.removeEventListener('keydown', handleKeydown)
|
||||
stopAutoPlay()
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
goToStep,
|
||||
resetGuide,
|
||||
currentStep,
|
||||
totalSteps,
|
||||
startAutoPlay,
|
||||
stopAutoPlay
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.guide-steps-container {
|
||||
@apply w-full h-full relative;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user