feat: 添加 Husky 和 commit 限制配置

This commit is contained in:
zhongyidian committed 2025-09-14 11:38:52 +08:00
commit 27e0c23b3f
125 files changed
+5760

No files matched your search

+233
View File
@@ -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>