feat: 添加思维导图支持,更新引导步骤组件,优化视频播放与学习进度追踪功能

This commit is contained in:
zhongyidian committed 2026-01-20 17:54:05 +08:00
1 parent e9a1fee27f
commit c4c9be9d35
98 files changed
+3178 -551

No files matched your search

+47 -14
View File
@@ -37,7 +37,10 @@
:total-steps="totalSteps"
@continue="handleNext"
>
{{ currentStepContent }}
<!-- 支持组件形式的 content -->
<component v-if="isCurrentStepComponent" :is="currentStepContentComponent" />
<!-- 支持字符串形式的 content -->
<div v-else v-html="currentStepContent"></div>
</GuideStepsItem>
</template>
</GuideDragon>
@@ -48,6 +51,7 @@
<script setup lang="ts">
import { ref, computed, useSlots, onMounted, onUnmounted, reactive } from 'vue'
import type { Component } from 'vue'
import SpotlightOverlay from './SpotlightOverlay.vue'
import GuideDragon from './GuideDragon.vue'
import GuideStepsItem from './GuideStepsItem.vue'
@@ -55,7 +59,7 @@ import GuideStepsItem from './GuideStepsItem.vue'
interface GuideStep {
id: string
title?: string
content: string
content: string | Component
nextText?: string
}
@@ -78,6 +82,8 @@ interface Props {
highlightMode?: 'border' | 'gradient'
/** 垂直偏移(仅Y轴),支持 number(px) 或 '10%'/'120px' */
positionOffsetY?: number | string
/** 是否可见,用于外部控制显示/隐藏 */
visible?: boolean
}
interface Emits {
@@ -95,14 +101,21 @@ const props = withDefaults(defineProps<Props>(), {
highlightPadding: 8,
highlightRadius: 16,
highlightMode: 'border',
positionOffsetY: '50%'
positionOffsetY: '50%',
visible: true
})
const emit = defineEmits<Emits>()
const slots = useSlots()
const show = ref(true)
const internalShow = ref(true)
const currentStepIndex = ref(0)
// 合并外部 visible 和内部 show 状态
// 只有当外部 visible 为 true 且内部 internalShow 也为 true 时才显示
const show = computed(() => {
return props.visible && internalShow.value
})
const alignStyle = reactive<{
left: string
top: string
@@ -116,7 +129,12 @@ const alignStyle = reactive<{
width: '1920px',
height: '1080px'
})
const highlightSelector = computed(() => props.highlightSelector || null)
// 验证并清理选择器,空字符串或只包含空格的选择器视为无效
const highlightSelector = computed(() => {
if (!props.highlightSelector) return null
const trimmed = props.highlightSelector.trim()
return trimmed.length > 0 ? trimmed : null
})
const highlightPadding = computed(() => props.highlightPadding || 0)
const highlightRadius = computed(() => props.highlightRadius || 0)
@@ -148,20 +166,34 @@ const currentStepComponent = computed(() => {
return slotSteps.value[currentStepIndex.value] || null
})
// 判断当前步骤的 content 是否为组件
const isCurrentStepComponent = computed(() => {
if (useSlotMode.value) return false
const step = currentStepData.value
if (!step) return false
return typeof step.content !== 'string'
})
// 当前步骤的内容(字符串形式)
const currentStepContent = computed(() => {
if (useSlotMode.value) return ''
if (isCurrentStepComponent.value) return ''
const step = currentStepData.value
if (!step) return ''
// if (!step.title) {
return `${step.content}`
// }
return typeof step.content === 'string' ? step.content : ''
})
// return `
// <h3 class="text-lg font-bold mb-2">${step.title}</h3>
// <div class="leading-relaxed">${step.content}</div>
// `
// 当前步骤的内容(组件形式)
const currentStepContentComponent = computed(() => {
if (useSlotMode.value) return null
if (!isCurrentStepComponent.value) return null
const step = currentStepData.value
if (!step) return null
return typeof step.content !== 'string' ? step.content : null
})
const handleNext = () => {
@@ -184,7 +216,7 @@ const handleSkip = () => {
}
const handleComplete = () => {
show.value = false
internalShow.value = false
emit('complete')
}
@@ -205,7 +237,7 @@ const goToStep = (index: number) => {
const resetGuide = () => {
currentStepIndex.value = 0
show.value = true
internalShow.value = true
}
// 键盘导航
@@ -309,6 +341,7 @@ defineExpose({
inset: 0;
z-index: 10001;
pointer-events: none;
font-family: 'DingTalkJinBuTi';
}
.guide-align {