feat: 提示词
This commit is contained in:
1 parent
b980f2993b
commit
6090abf955
15 files changed
+330
-54
No files matched your search
Binary file not shown.
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
<div class="app-container" :style="containerStyle">
|
<div class="app-container" :style="containerStyle">
|
||||||
<div class="app-content" :style="scaleStyle">
|
<div class="app-content" :style="scaleStyle">
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<KeepAlive :include="[]">
|
<KeepAlive :include="['Action']">
|
||||||
<component :is="Component" />
|
<component :is="Component" />
|
||||||
</KeepAlive>
|
</KeepAlive>
|
||||||
</router-view>
|
</router-view>
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 129 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 123 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
@@ -137,7 +137,7 @@ const positionStyle = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.guide-panel {
|
.guide-panel {
|
||||||
@apply relative bg-white rounded-2xl shadow-2xl;
|
@apply relative bg-white rounded-2xl;
|
||||||
/* background: linear-gradient(135deg, #60a5fa 0%, #a855f7 100%);
|
/* background: linear-gradient(135deg, #60a5fa 0%, #a855f7 100%);
|
||||||
border: 2px solid #60a5fa;
|
border: 2px solid #60a5fa;
|
||||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); */
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); */
|
||||||
@@ -170,7 +170,7 @@ const positionStyle = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.guide-content {
|
.guide-content {
|
||||||
@apply flex items-start p-6 h-full;
|
@apply flex items-start pl-6 pt-9 pb-0 h-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-icon {
|
.content-icon {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
:target-selector="highlightSelector"
|
:target-selector="highlightSelector"
|
||||||
:padding="highlightPadding"
|
:padding="highlightPadding"
|
||||||
:border-radius="highlightRadius"
|
:border-radius="highlightRadius"
|
||||||
|
:highlight-mode="highlightMode"
|
||||||
/>
|
/>
|
||||||
<div class="guide-align" :style="alignStyle">
|
<div class="guide-align" :style="alignStyle">
|
||||||
<GuideDragon
|
<GuideDragon
|
||||||
@@ -73,6 +74,8 @@ interface Props {
|
|||||||
highlightPadding?: number
|
highlightPadding?: number
|
||||||
/** 高亮圆角,默认16 */
|
/** 高亮圆角,默认16 */
|
||||||
highlightRadius?: number
|
highlightRadius?: number
|
||||||
|
/** 高亮模式:'border' 画框效果,'gradient' 渐变灯光效果,默认 'border' */
|
||||||
|
highlightMode?: 'border' | 'gradient'
|
||||||
/** 垂直偏移(仅Y轴),支持 number(px) 或 '10%'/'120px' */
|
/** 垂直偏移(仅Y轴),支持 number(px) 或 '10%'/'120px' */
|
||||||
positionOffsetY?: number | string
|
positionOffsetY?: number | string
|
||||||
}
|
}
|
||||||
@@ -91,6 +94,7 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
highlightSelector: null,
|
highlightSelector: null,
|
||||||
highlightPadding: 8,
|
highlightPadding: 8,
|
||||||
highlightRadius: 16,
|
highlightRadius: 16,
|
||||||
|
highlightMode: 'border',
|
||||||
positionOffsetY: '50%'
|
positionOffsetY: '50%'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="visible" class="spotlight-overlay">
|
<div v-if="visible" class="spotlight-overlay">
|
||||||
|
<!-- 渐变遮罩层 -->
|
||||||
|
<div v-if="props.highlightMode === 'gradient'" class="spotlight-mask" :style="maskStyle"></div>
|
||||||
|
<!-- 画框模式:使用 box-shadow 遮罩 + 边框 -->
|
||||||
<div
|
<div
|
||||||
|
v-if="props.highlightMode === 'border' && rect.width > 0 && rect.height > 0"
|
||||||
class="spotlight-hole"
|
class="spotlight-hole"
|
||||||
:style="{
|
:style="holeStyle"
|
||||||
top: rect.top + 'px',
|
|
||||||
left: rect.left + 'px',
|
|
||||||
width: rect.width + 'px',
|
|
||||||
height: rect.height + 'px',
|
|
||||||
borderRadius: borderRadius + 'px',
|
|
||||||
boxShadow: `0 0 0 9999px rgba(0,0,0,0.6)`
|
|
||||||
}"
|
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, watch, onMounted, onBeforeUnmount } from 'vue'
|
import { reactive, computed, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** 是否可见 */
|
/** 是否可见 */
|
||||||
@@ -26,13 +23,19 @@ interface Props {
|
|||||||
padding?: number
|
padding?: number
|
||||||
/** 圆角 */
|
/** 圆角 */
|
||||||
borderRadius?: number
|
borderRadius?: number
|
||||||
|
/** 渐变过渡范围(像素) */
|
||||||
|
gradientRange?: number
|
||||||
|
/** 高亮模式:'border' 画框效果,'gradient' 渐变灯光效果 */
|
||||||
|
highlightMode?: 'border' | 'gradient'
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
visible: false,
|
visible: false,
|
||||||
targetSelector: null,
|
targetSelector: null,
|
||||||
padding: 8,
|
padding: 8,
|
||||||
borderRadius: 16
|
borderRadius: 16,
|
||||||
|
gradientRange: 100,
|
||||||
|
highlightMode: 'border'
|
||||||
})
|
})
|
||||||
|
|
||||||
const rect = reactive({ top: 0, left: 0, width: 0, height: 0 })
|
const rect = reactive({ top: 0, left: 0, width: 0, height: 0 })
|
||||||
@@ -72,7 +75,78 @@ onBeforeUnmount(() => {
|
|||||||
window.removeEventListener('scroll', onScroll)
|
window.removeEventListener('scroll', onScroll)
|
||||||
})
|
})
|
||||||
|
|
||||||
const borderRadius = props.borderRadius
|
// 计算渐变遮罩样式
|
||||||
|
const maskStyle = computed(() => {
|
||||||
|
if (!props.visible || rect.width === 0 || rect.height === 0) {
|
||||||
|
return {
|
||||||
|
maskImage: 'none',
|
||||||
|
WebkitMaskImage: 'none'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算中心点坐标(相对于视口)
|
||||||
|
const centerX = rect.left + rect.width / 2
|
||||||
|
const centerY = rect.top + rect.height / 2
|
||||||
|
|
||||||
|
// 计算椭圆半径(基于高亮区域)
|
||||||
|
// 使用较大的尺寸作为基准,确保覆盖整个高亮区域
|
||||||
|
const maxDimension = Math.max(rect.width, rect.height)
|
||||||
|
const aspectRatio = rect.width / rect.height
|
||||||
|
|
||||||
|
// 渐变范围
|
||||||
|
const gradientRangeValue = Math.max(0, props.gradientRange)
|
||||||
|
|
||||||
|
// 计算椭圆的半径(考虑宽高比)
|
||||||
|
// 对于椭圆,我们需要分别计算 x 和 y 方向的半径
|
||||||
|
const baseRadius = maxDimension / 2
|
||||||
|
const radiusX = baseRadius + gradientRangeValue
|
||||||
|
const radiusY = (baseRadius + gradientRangeValue) / aspectRatio
|
||||||
|
|
||||||
|
// 高亮区域边缘的半径(椭圆的主轴)
|
||||||
|
const highlightRadius = baseRadius
|
||||||
|
|
||||||
|
// 渐变停止点:从中心透明,到高亮边缘仍透明,然后向外渐变到不透明
|
||||||
|
// 计算渐变停止点的像素值
|
||||||
|
const highlightEdge = highlightRadius // 高亮区域边缘,保持透明
|
||||||
|
const gradientStart = highlightRadius + gradientRangeValue * 0.1 // 渐变开始
|
||||||
|
const gradientMid = highlightRadius + gradientRangeValue * 0.5 // 渐变中间
|
||||||
|
const gradientEnd = highlightRadius + gradientRangeValue // 渐变结束,完全不透明
|
||||||
|
|
||||||
|
// 创建径向渐变遮罩
|
||||||
|
// mask-image 中,transparent 表示遮罩透明(背景可见),black 表示遮罩不透明(背景被遮住)
|
||||||
|
// 我们需要从中心透明(高亮区域可见)渐变到边缘不透明(周围变暗)
|
||||||
|
// 使用平滑的多色停止点创建柔和的渐变效果
|
||||||
|
const gradient = `radial-gradient(
|
||||||
|
ellipse ${radiusX}px ${radiusY}px at ${centerX}px ${centerY}px,
|
||||||
|
transparent 0px,
|
||||||
|
transparent ${highlightEdge}px,
|
||||||
|
rgba(0, 0, 0, 0.2) ${gradientStart}px,
|
||||||
|
rgba(0, 0, 0, 0.5) ${gradientMid}px,
|
||||||
|
rgba(0, 0, 0, 0.9) ${gradientEnd * 0.9}px,
|
||||||
|
black ${gradientEnd}px
|
||||||
|
)`
|
||||||
|
|
||||||
|
return {
|
||||||
|
maskImage: gradient,
|
||||||
|
WebkitMaskImage: gradient
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算画框模式样式(box-shadow + 边框)
|
||||||
|
const holeStyle = computed(() => {
|
||||||
|
if (!props.visible || rect.width === 0 || rect.height === 0) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: rect.top + 'px',
|
||||||
|
left: rect.left + 'px',
|
||||||
|
width: rect.width + 'px',
|
||||||
|
height: rect.height + 'px',
|
||||||
|
borderRadius: props.borderRadius + 'px',
|
||||||
|
boxShadow: `0 0 0 9999px rgba(0,0,0,0.6)`
|
||||||
|
}
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -83,8 +157,26 @@ const borderRadius = props.borderRadius
|
|||||||
z-index: 100000;
|
z-index: 100000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spotlight-hole {
|
.spotlight-mask {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: rgba(0, 0, 0, 0.6);
|
||||||
|
transition: opacity 0.3s ease-in-out;
|
||||||
|
mask-image: radial-gradient(ellipse, transparent 0%, black 100%);
|
||||||
|
-webkit-mask-image: radial-gradient(ellipse, transparent 0%, black 100%);
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spotlight-hole {
|
||||||
|
position: absolute;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.9);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 1px rgba(0, 0, 0, 0.3),
|
||||||
|
0 0 20px rgba(255, 255, 255, 0.3),
|
||||||
|
0 0 0 9999px rgba(0, 0, 0, 0.6);
|
||||||
|
pointer-events: none;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -3,6 +3,15 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
/* @import "./assets/styles/tailwind.css"; */
|
/* @import "./assets/styles/tailwind.css"; */
|
||||||
|
|
||||||
|
/* 定义字体 */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'DingTalkJinBuTi';
|
||||||
|
src: url('/fonts/DingTalk-JinBuTi.otf') format('opentype');
|
||||||
|
font-weight: bold;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
/* 重置样式 */
|
/* 重置样式 */
|
||||||
* {
|
* {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|||||||
@@ -1,19 +1,26 @@
|
|||||||
<script lang="tsx" setup>
|
<script lang="tsx" setup>
|
||||||
import { getBackgroundImage, IMAGE_PATHS } from '@/utils'
|
import { getBackgroundImage, getStaticImage, IMAGE_PATHS } from '@/utils'
|
||||||
import { ImgButton } from '@/components/Button'
|
import { ImgButton } from '@/components/Button'
|
||||||
|
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { ProgressBar, Overlay } from '@/components'
|
import { ProgressBar, Overlay } from '@/components'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useFilmStore } from '@/stores'
|
import { useFilmStore } from '@/stores'
|
||||||
|
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
|
||||||
import FilmMenu from './components/menu.vue'
|
import FilmMenu from './components/menu.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const film = useFilmStore()
|
const film = useFilmStore()
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
|
|
||||||
|
const status = ref(true)
|
||||||
|
|
||||||
const currentStep = computed(() => film.FilmUnlockProgress)
|
const currentStep = computed(() => film.FilmUnlockProgress)
|
||||||
|
|
||||||
|
const progress = computed(() => {
|
||||||
|
return Math.round((currentStep.value / 4) * 100)
|
||||||
|
})
|
||||||
|
|
||||||
const handleComplete = () => {
|
const handleComplete = () => {
|
||||||
// console.log('完成前置条件')
|
// console.log('完成前置条件')
|
||||||
// 处理完成逻辑
|
// 处理完成逻辑
|
||||||
@@ -51,6 +58,16 @@ onMounted(() => {
|
|||||||
:style="{ backgroundImage: getBackgroundImage('film/电影模块背景') }"
|
:style="{ backgroundImage: getBackgroundImage('film/电影模块背景') }"
|
||||||
></div>
|
></div>
|
||||||
|
|
||||||
|
<!-- 进度数据 -->
|
||||||
|
<div class="absolute top-[140px] left-[calc(50%)] -translate-x-1/2">
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-center text-white [font-family:DingTalkJinBuTi] font-[1000] [text-shadow:_6px_6px_4px_#2b80c1,_6px_6px_4px_#2b80c1,_6px_6px_4px_#2b80c1,_6px_6px_4px_#2b80c1]"
|
||||||
|
>
|
||||||
|
<span class="text-6xl">学习进度:</span>
|
||||||
|
<span class="text-7xl">{{ progress }}%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 关闭 -->
|
<!-- 关闭 -->
|
||||||
<ImgButton
|
<ImgButton
|
||||||
class="absolute right-0 top-0 z-20"
|
class="absolute right-0 top-0 z-20"
|
||||||
@@ -112,5 +129,23 @@ onMounted(() => {
|
|||||||
@step-change="handleStepChange"
|
@step-change="handleStepChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<GuideSteps
|
||||||
|
v-if="status"
|
||||||
|
highlight-selector=""
|
||||||
|
highlight-mode="gradient"
|
||||||
|
position-offset-y="85%"
|
||||||
|
@complete="() => {}"
|
||||||
|
>
|
||||||
|
<GuideStepsItem>
|
||||||
|
<div class="flex items-center justify-start">
|
||||||
|
请点击<img
|
||||||
|
class="w-20"
|
||||||
|
:src="getStaticImage('film/icon')"
|
||||||
|
alt=""
|
||||||
|
/>进入任务取景框开始学习!
|
||||||
|
</div>
|
||||||
|
</GuideStepsItem>
|
||||||
|
</GuideSteps>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -154,7 +154,7 @@ const handleComplete = () => {
|
|||||||
<img
|
<img
|
||||||
ref="backgroundImage"
|
ref="backgroundImage"
|
||||||
class="absolute inset-0 w-full h-full object-cover pointer-events-none opacity-0"
|
class="absolute inset-0 w-full h-full object-cover pointer-events-none opacity-0"
|
||||||
:src="getStaticImage('film/shooting/龙美术馆清晰图.png')"
|
:src="getStaticImage('film/shooting/油罐艺术公园清晰底图.png')"
|
||||||
alt=""
|
alt=""
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import { computed, nextTick, onMounted, ref } from 'vue'
|
import { computed, nextTick, onMounted, ref } from 'vue'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import ImgButton from '@/components/Button/ImgButton.vue'
|
import ImgButton from '@/components/Button/ImgButton.vue'
|
||||||
import { GuideSteps, StepNavigation } from '@/components/Guide'
|
import { GuideSteps, GuideStepsItem, StepNavigation } from '@/components/Guide'
|
||||||
import { Video, QuestionQuiz, type Question } from '@/components'
|
import { Video, QuestionQuiz, type Question } from '@/components'
|
||||||
import { getStaticImage, IMAGE_PATHS } from '@/utils'
|
import { getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils'
|
||||||
import second from '/video/vlog_renwuqujing_jichujingbieyunyong.mp4'
|
import second from '/video/vlog_renwuqujing_jichujingbieyunyong.mp4'
|
||||||
import third from '/video/vlog_renwuqujing_huamiangoutu.mp4'
|
import third from '/video/vlog_renwuqujing_huamiangoutu.mp4'
|
||||||
import fourth from '/video/vlog_renwuqujing_jiaobencehua.mp4'
|
import fourth from '/video/vlog_renwuqujing_jiaobencehua.mp4'
|
||||||
@@ -17,6 +17,10 @@ const route = useRoute()
|
|||||||
|
|
||||||
const film = useFilmStore()
|
const film = useFilmStore()
|
||||||
|
|
||||||
|
const visibleGuide = ref(true)
|
||||||
|
|
||||||
|
const visibleGuide2 = ref(true)
|
||||||
|
|
||||||
// 受控播放开关(用于切换到下一个视频时自动播放)
|
// 受控播放开关(用于切换到下一个视频时自动播放)
|
||||||
const playing = ref(false)
|
const playing = ref(false)
|
||||||
const status = ref(false)
|
const status = ref(false)
|
||||||
@@ -34,43 +38,43 @@ const steps = ref([
|
|||||||
const navigationSteps = ref([
|
const navigationSteps = ref([
|
||||||
{
|
{
|
||||||
id: 'step1',
|
id: 'step1',
|
||||||
label: '',
|
label: '后期制作',
|
||||||
content: '遮幅',
|
content: '遮幅',
|
||||||
type: 'shooting' as const
|
type: 'post' as const
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'step2',
|
id: 'step2',
|
||||||
label: '',
|
label: '拍摄执行',
|
||||||
content: '光线',
|
content: '光线',
|
||||||
type: 'shooting' as const
|
type: 'post' as const
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'step3',
|
id: 'step3',
|
||||||
label: '',
|
label: '拍摄执行',
|
||||||
content: '虚实',
|
content: '虚实',
|
||||||
type: 'planning' as const
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'step4',
|
|
||||||
label: '',
|
|
||||||
content: '特写镜头',
|
|
||||||
type: 'shooting' as const
|
type: 'shooting' as const
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'step4',
|
||||||
|
label: '拍摄执行',
|
||||||
|
content: '特写镜头',
|
||||||
|
type: 'post' as const
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'step5',
|
id: 'step5',
|
||||||
label: '',
|
label: '拍摄执行',
|
||||||
content: '分镜头',
|
content: '分镜头',
|
||||||
type: 'post' as const
|
type: 'post' as const
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'step5',
|
id: 'step5',
|
||||||
label: '',
|
label: '后期制作',
|
||||||
content: '滤镜',
|
content: '滤镜',
|
||||||
type: 'post' as const
|
type: 'post' as const
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'step5',
|
id: 'step5',
|
||||||
label: '',
|
label: '拍摄执行',
|
||||||
content: '同期声',
|
content: '同期声',
|
||||||
type: 'post' as const
|
type: 'post' as const
|
||||||
}
|
}
|
||||||
@@ -140,7 +144,7 @@ const getQuestionsForVideo = (index: number): Question[] => {
|
|||||||
//按顺序播放
|
//按顺序播放
|
||||||
const currentVideo = computed(() => videoList[currentVideoIndex.value])
|
const currentVideo = computed(() => videoList[currentVideoIndex.value])
|
||||||
const resolvedSrc = computed(() => {
|
const resolvedSrc = computed(() => {
|
||||||
if (key.value === 'case') return '/video/anlishangxi_meishi_xiaoyuanzi.mp4'
|
if (key.value === 'case') return getPublicPath('video/anlishangxi_meishi_xiaoyuanzi.mp4')
|
||||||
if (key.value === 'profession') return currentVideo.value
|
if (key.value === 'profession') return currentVideo.value
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
@@ -248,11 +252,11 @@ onMounted(() => {
|
|||||||
<!-- 步骤导航按钮 -->
|
<!-- 步骤导航按钮 -->
|
||||||
<button
|
<button
|
||||||
v-if="key === 'profession'"
|
v-if="key === 'profession'"
|
||||||
class="absolute right-4 top-1/2 z-10 -translate-y-1/2 w-12 h-12 bg-blue-500/80 hover:bg-blue-500 rounded-full flex items-center justify-center text-white transition-all duration-200"
|
class="absolute right-4 top-1/2 z-10 -translate-y-1/2 w-20 h-20 bg-blue-500/80 hover:bg-blue-500 rounded-full flex items-center justify-center text-white transition-all duration-200"
|
||||||
@click="showStepNavigation = true"
|
@click="showStepNavigation = true"
|
||||||
title="步骤导航"
|
title="步骤导航"
|
||||||
>
|
>
|
||||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
<svg width="35" height="35" viewBox="0 0 20 20" fill="none">
|
||||||
<path
|
<path
|
||||||
d="M3 4H17M3 8H17M3 12H17M3 16H17"
|
d="M3 4H17M3 8H17M3 12H17M3 16H17"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -293,7 +297,7 @@ onMounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 引导与高亮 -->
|
<!-- 引导与高亮 -->
|
||||||
<GuideSteps v-if="status" :steps="steps" position-offset-y="85%" @complete="handleComplete" />
|
<!-- <GuideSteps v-if="status" :steps="steps" position-offset-y="85%" @complete="handleComplete" /> -->
|
||||||
|
|
||||||
<!-- 步骤导航抽屉 -->
|
<!-- 步骤导航抽屉 -->
|
||||||
<StepNavigation
|
<StepNavigation
|
||||||
@@ -304,6 +308,52 @@ onMounted(() => {
|
|||||||
@step-click="handleStepClick"
|
@step-click="handleStepClick"
|
||||||
@close="handleStepNavigationClose"
|
@close="handleStepNavigationClose"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 引导与高亮 -->
|
||||||
|
<GuideSteps
|
||||||
|
highlight-selector=""
|
||||||
|
v-if="visibleGuide && key === 'profession'"
|
||||||
|
position-offset-y="85%"
|
||||||
|
>
|
||||||
|
<GuideStepsItem>
|
||||||
|
从刚才的案例可以看出,电影感画面自带一种独特的艺术魅力——或大气恢弘,扑朔迷离或充满故事感。
|
||||||
|
</GuideStepsItem>
|
||||||
|
<GuideStepsItem>
|
||||||
|
作为短视频创作者,我们可以借助这些方法,在拍摄与剪辑中打造更有质感的画面。
|
||||||
|
</GuideStepsItem>
|
||||||
|
<GuideStepsItem>
|
||||||
|
<div class="flex flex-wrap items-center justify-start">
|
||||||
|
<div>接下来,让我们一起看看打造电影感的几种关键技巧吧。</div>
|
||||||
|
<div class="text-yellow-400">
|
||||||
|
点击<button
|
||||||
|
class="w-10 h-10 bg-blue-500/80 hover:bg-blue-500 rounded-full inline-flex items-center justify-center text-white transition-all duration-200"
|
||||||
|
title="步骤导航"
|
||||||
|
>
|
||||||
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||||
|
<path
|
||||||
|
d="M3 4H17M3 8H17M3 12H17M3 16H17"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
可以切换不同的学习内容!
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</GuideStepsItem>
|
||||||
|
</GuideSteps>
|
||||||
|
|
||||||
|
<!-- 引导与高亮 -->
|
||||||
|
<GuideSteps highlight-selector="" v-if="visibleGuide2 && key === 'idea'" position-offset-y="85%">
|
||||||
|
<GuideStepsItem>
|
||||||
|
一个好镜头,既要好看,又要好懂 ——
|
||||||
|
既有视觉吸引力,又能把信息清晰地传达出来。当这些条件被满足,一个镜头就有了“高级感”。
|
||||||
|
</GuideStepsItem>
|
||||||
|
<GuideStepsItem>
|
||||||
|
而优秀的短视频,就是靠无数这样的好镜头累积而成的。接下来,我们就以徐汇滨江为例,一起动手练习如何拍出属于它的意义与故事吧!
|
||||||
|
</GuideStepsItem>
|
||||||
|
</GuideSteps>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { ref, computed } from 'vue'
|
|||||||
import { getBackgroundImage, IMAGE_PATHS, getStaticImage } from '@/utils'
|
import { getBackgroundImage, IMAGE_PATHS, getStaticImage } from '@/utils'
|
||||||
import { ImgButton } from '@/components/Button'
|
import { ImgButton } from '@/components/Button'
|
||||||
import WorkSideSwitcher from './components/WorkSideSwitcher.vue'
|
import WorkSideSwitcher from './components/WorkSideSwitcher.vue'
|
||||||
import { GuideSteps } from '@/components/Guide'
|
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useScale } from '@/composables/useScale'
|
import { useScale } from '@/composables/useScale'
|
||||||
import { ImagePreview } from '@/components'
|
import { ImagePreview } from '@/components'
|
||||||
@@ -23,15 +23,6 @@ const workSideSwitcherRef = ref<InstanceType<typeof WorkSideSwitcher>>()
|
|||||||
const isGuideFilmTask = computed(() => film.isGuideFilmTask)
|
const isGuideFilmTask = computed(() => film.isGuideFilmTask)
|
||||||
const isFilmUnlock = computed(() => film.isFilmUnlock)
|
const isFilmUnlock = computed(() => film.isFilmUnlock)
|
||||||
|
|
||||||
//引导步骤
|
|
||||||
const steps = [
|
|
||||||
{
|
|
||||||
id: 'step1',
|
|
||||||
content: '经常听到有人赞叹:“这个镜头真高级。”'
|
|
||||||
},
|
|
||||||
{ id: 'step2', content: '那么什么样的镜头才算是好镜头?又是什么让画面显得更高级?' }
|
|
||||||
]
|
|
||||||
|
|
||||||
//切换图片
|
//切换图片
|
||||||
const sideSwitcherItems = ref([
|
const sideSwitcherItems = ref([
|
||||||
{
|
{
|
||||||
@@ -152,6 +143,21 @@ function openImages(urls: string[], startIndex = 0) {
|
|||||||
:src="getStaticImage(currentItem?.image || '')"
|
:src="getStaticImage(currentItem?.image || '')"
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- <div class="absolute inset-0 flex items-center justify-center pointer-events-none z-10">
|
||||||
|
<div
|
||||||
|
class="w-40 h-40 pointer-events-auto rounded-full bg-black/50 backdrop-blur-sm flex items-center justify-center cursor-pointer hover:bg-black/70 transition-colors"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
class="w-22 h-22 text-white ml-1"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path d="M8 5v14l11-7z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 左侧选项切换 -->
|
<!-- 左侧选项切换 -->
|
||||||
@@ -174,7 +180,6 @@ function openImages(urls: string[], startIndex = 0) {
|
|||||||
<!-- 引导与高亮 -->
|
<!-- 引导与高亮 -->
|
||||||
<GuideSteps
|
<GuideSteps
|
||||||
v-if="isGuideFilmTask"
|
v-if="isGuideFilmTask"
|
||||||
:steps="steps"
|
|
||||||
:highlight-selector="currentHighlight"
|
:highlight-selector="currentHighlight"
|
||||||
:highlight-padding="12"
|
:highlight-padding="12"
|
||||||
:highlight-radius="20"
|
:highlight-radius="20"
|
||||||
@@ -182,7 +187,20 @@ function openImages(urls: string[], startIndex = 0) {
|
|||||||
@complete="handleGuideComplete"
|
@complete="handleGuideComplete"
|
||||||
@skip="handleGuideComplete"
|
@skip="handleGuideComplete"
|
||||||
position-offset-y="85%"
|
position-offset-y="85%"
|
||||||
/>
|
>
|
||||||
|
<GuideStepsItem>
|
||||||
|
点击右侧【案例赏析】、【专业剖析】、【思路提点】,可以切换不同内容
|
||||||
|
</GuideStepsItem>
|
||||||
|
<GuideStepsItem>
|
||||||
|
<div class="flex items-center justify-start">
|
||||||
|
请点击 <img
|
||||||
|
class="w-16"
|
||||||
|
:src="getStaticImage('film/sx')"
|
||||||
|
alt=""
|
||||||
|
/> ,进入案例赏析!
|
||||||
|
</div>
|
||||||
|
</GuideStepsItem>
|
||||||
|
</GuideSteps>
|
||||||
|
|
||||||
<!-- 引导词按钮 -->
|
<!-- 引导词按钮 -->
|
||||||
<ImgButton
|
<ImgButton
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
defineOptions({
|
||||||
|
name: 'Action'
|
||||||
|
})
|
||||||
import { getBackgroundImage, IMAGE_PATHS, getStaticImage } from '@/utils'
|
import { getBackgroundImage, IMAGE_PATHS, getStaticImage } from '@/utils'
|
||||||
import { ImgButton } from '@/components/Button'
|
import { ImgButton } from '@/components/Button'
|
||||||
import { ModuleSwitcher } from './components/ModuleSwitcher'
|
import { ModuleSwitcher } from './components/ModuleSwitcher'
|
||||||
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
import { computed, ref, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useUserStore } from '@/stores'
|
import { useUserStore } from '@/stores'
|
||||||
|
import { GuideSteps, GuideStepsItem } from '@/components/Guide'
|
||||||
|
|
||||||
|
//点击电视机按钮引导状态
|
||||||
|
const status = ref(false)
|
||||||
|
//点击新手导航节点引导状态
|
||||||
|
const status2 = ref(true)
|
||||||
|
|
||||||
|
const handleComplete = () => {
|
||||||
|
status.value = false
|
||||||
|
}
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const user = useUserStore()
|
const user = useUserStore()
|
||||||
@@ -46,6 +59,7 @@ const handleCloseGuide = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleModuleChange = (module: 'vlog' | 'film' | 'interview') => {
|
const handleModuleChange = (module: 'vlog' | 'film' | 'interview') => {
|
||||||
|
status.value = true
|
||||||
if (selectedModule.value === module) return
|
if (selectedModule.value === module) return
|
||||||
|
|
||||||
isImageLoading.value = true
|
isImageLoading.value = true
|
||||||
@@ -58,7 +72,8 @@ const handleModuleChange = (module: 'vlog' | 'film' | 'interview') => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
console.log(selectedModule.value.charAt(0).toUpperCase() + selectedModule.value.slice(1))
|
if (selectedModule.value === 'interview') return
|
||||||
|
// console.log(selectedModule.value.charAt(0).toUpperCase() + selectedModule.value.slice(1))
|
||||||
router.push({
|
router.push({
|
||||||
//首字母大写
|
//首字母大写
|
||||||
name: selectedModule.value.charAt(0).toUpperCase() + selectedModule.value.slice(1)
|
name: selectedModule.value.charAt(0).toUpperCase() + selectedModule.value.slice(1)
|
||||||
@@ -173,7 +188,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
<!-- 电视机 -->
|
<!-- 电视机 -->
|
||||||
<div
|
<div
|
||||||
class="absolute w-[765px] h-[768px] right-0 top-0 z-[5] transition-all duration-300 hover:scale-105 hover:brightness-110 cursor-pointer"
|
class="absolute w-[765px] h-[768px] right-0 top-0 z-[5] transition-all duration-300 hover:scale-105 hover:brightness-110 cursor-pointer tvGuide"
|
||||||
:class="{ 'opacity-50': isImageLoading }"
|
:class="{ 'opacity-50': isImageLoading }"
|
||||||
:style="{ backgroundImage: selectedImage }"
|
:style="{ backgroundImage: selectedImage }"
|
||||||
@click.stop="handleClick"
|
@click.stop="handleClick"
|
||||||
@@ -197,7 +212,7 @@ onUnmounted(() => {
|
|||||||
v-if="!guideShow"
|
v-if="!guideShow"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="absolute bottom-20 right-[calc(50%-20px)] translate-x-1/2 w-20 h-20 cursor-pointer pointer-events-auto"
|
class="absolute bottom-20 right-[calc(50%-20px)] translate-x-1/2 w-20 h-20 cursor-pointer pointer-events-auto newUserGuide"
|
||||||
@click="handleOpenGuide"
|
@click="handleOpenGuide"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -329,5 +344,58 @@ onUnmounted(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 引导与高亮 -->
|
||||||
|
<GuideSteps
|
||||||
|
v-if="status"
|
||||||
|
highlight-mode="gradient"
|
||||||
|
highlight-selector=".tvGuide"
|
||||||
|
position-offset-y="85%"
|
||||||
|
@complete="handleComplete"
|
||||||
|
>
|
||||||
|
<GuideStepsItem>
|
||||||
|
<div
|
||||||
|
class="flex items-center justify-start"
|
||||||
|
v-if="selectedModule === 'interview' || selectedModule === 'vlog'"
|
||||||
|
>
|
||||||
|
[{{
|
||||||
|
selectedModule == 'interview'
|
||||||
|
? '访谈-聆听与对话'
|
||||||
|
: selectedModule == 'vlog'
|
||||||
|
? 'vlog-创作与表达'
|
||||||
|
: ''
|
||||||
|
}}]模块建设中...
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center justify-start" v-else>
|
||||||
|
请点击<img
|
||||||
|
class="w-20"
|
||||||
|
:src="getStaticImage(imageMap[selectedModule])"
|
||||||
|
alt=""
|
||||||
|
/>,继续学习[{{
|
||||||
|
selectedModule == 'film'
|
||||||
|
? '电影-创作与表达'
|
||||||
|
: selectedModule == 'vlog'
|
||||||
|
? 'vlog-创作与表达'
|
||||||
|
: ''
|
||||||
|
}}]模块!
|
||||||
|
</div>
|
||||||
|
</GuideStepsItem>
|
||||||
|
</GuideSteps>
|
||||||
|
|
||||||
|
<GuideSteps
|
||||||
|
v-if="status2"
|
||||||
|
highlight-mode="gradient"
|
||||||
|
highlight-selector=".newUserGuide"
|
||||||
|
position-offset-y="50%"
|
||||||
|
@complete="handleComplete"
|
||||||
|
>
|
||||||
|
<GuideStepsItem>
|
||||||
|
<div class="flex items-center justify-start">
|
||||||
|
欢迎来到《短视频达人养成记》课程,请点击下方
|
||||||
|
<img class="w-20" :src="getStaticImage('home/action/top')" alt="" />
|
||||||
|
开始学习!
|
||||||
|
</div>
|
||||||
|
</GuideStepsItem>
|
||||||
|
</GuideSteps>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -4,7 +4,7 @@ import { useRouter, useRoute } from 'vue-router'
|
|||||||
import ImgButton from '@/components/Button/ImgButton.vue'
|
import ImgButton from '@/components/Button/ImgButton.vue'
|
||||||
import { GuideSteps, StepNavigation } from '@/components/Guide'
|
import { GuideSteps, StepNavigation } from '@/components/Guide'
|
||||||
import { Video } from '@/components'
|
import { Video } from '@/components'
|
||||||
import { getBackgroundImage, getStaticImage, IMAGE_PATHS } from '@/utils'
|
import { getBackgroundImage, getPublicPath, getStaticImage, IMAGE_PATHS } from '@/utils'
|
||||||
import second from '/video/vlog_renwuqujing_jichujingbieyunyong.mp4'
|
import second from '/video/vlog_renwuqujing_jichujingbieyunyong.mp4'
|
||||||
import third from '/video/vlog_renwuqujing_huamiangoutu.mp4'
|
import third from '/video/vlog_renwuqujing_huamiangoutu.mp4'
|
||||||
import fourth from '/video/vlog_renwuqujing_jiaobencehua.mp4'
|
import fourth from '/video/vlog_renwuqujing_jiaobencehua.mp4'
|
||||||
@@ -74,7 +74,7 @@ const currentVideoIndex = ref(0)
|
|||||||
//按顺序播放
|
//按顺序播放
|
||||||
const currentVideo = computed(() => videoList[currentVideoIndex.value])
|
const currentVideo = computed(() => videoList[currentVideoIndex.value])
|
||||||
const resolvedSrc = computed(() => {
|
const resolvedSrc = computed(() => {
|
||||||
if (key.value === 'case') return '/video/anlishangxi_meishi_xiaoyuanzi.mp4'
|
if (key.value === 'case') return getPublicPath('video/anlishangxi_meishi_xiaoyuanzi.mp4')
|
||||||
if (key.value === 'profession') return currentVideo.value
|
if (key.value === 'profession') return currentVideo.value
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in new issue
Block a user