Files
super_video/src/views/skill/index.vue
T

324 lines
8.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="tsx" setup>
defineOptions({ name: 'SkillPage' })
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { getBackgroundImage, IMAGE_PATHS } from '@/utils'
import { ImgButton } from '@/components/Button'
import ModuleSwitcher from './components/ModuleSwitcher.vue'
import CategoryTags from './components/CategoryTags.vue'
import IconLayer from './components/IconLayer.vue'
import { GuideSteps } from '@/components/Guide'
import KnowledgeCard from '@/components/KnowledgeCard/KnowledgeCard.vue'
import { useSkillState, type KnowledgeBtn } from './useSkillState'
const router = useRouter()
// 仅从 hook 获取必要的业务状态与意图判断
const {
currentModule,
nextModule,
prevModule,
selectedCategory,
handleCategoryClick,
currentCategoryConfig,
displayedIcons,
showSubIcons,
selectedPrimaryIcon,
getIconAction
} = useSkillState()
// 页面 UI 状态(留在组件内)
const status = ref(false)
setTimeout(() => {
status.value = true
}, 1000)
const sideStatus = ref(true)
// 引导
const stepIndex = ref(0)
type SimpleStep = { id: string; content: string }
const steps: SimpleStep[] = [{ id: 'step1', content: '可以点击左右箭头切换到对应模块的知识树哦' }]
const currentHighlight = computed(() =>
stepIndex.value === 0 ? '.img-switch-item--vlog' : '.work-border'
)
const handleStepChange = (_step: SimpleStep | null, index: number) => {
stepIndex.value = index
}
// 模块切换(本地完成联动重置)
const onNextModule = () => {
nextModule()
if (selectedCategory.value) {
selectedCategory.value = ''
sideStatus.value = true
}
showSubIcons.value = false
selectedPrimaryIcon.value = null
}
const onPrevModule = () => {
prevModule()
if (selectedCategory.value) {
selectedCategory.value = ''
sideStatus.value = true
}
showSubIcons.value = false
selectedPrimaryIcon.value = null
}
// 分类选择(保持原交互)
const onCategorySelect = (categoryName: string) => {
handleCategoryClick(categoryName)
sideStatus.value = false
showSubIcons.value = false
selectedPrimaryIcon.value = null
}
// 返回按钮相关
const handleBackToMain = () => {
selectedCategory.value = ''
sideStatus.value = true
showSubIcons.value = false
selectedPrimaryIcon.value = null
}
const backButtonTooltip = computed(() => {
if (showSubIcons.value) return '返回上一步'
return selectedCategory.value ? '返回主视图' : '退出页面'
})
const handleClose = () => {
if (showSubIcons.value) {
showSubIcons.value = false
selectedPrimaryIcon.value = null
return
}
if (selectedCategory.value) {
handleBackToMain()
} else {
router.back()
}
}
// 知识卡片(本地维护)
const showKnowledgeCard = ref(false)
const currentKnowledge = ref<KnowledgeBtn | null>(null)
const knowledgeMap: Record<'三分法构图' | '居中构图' | '对角线构图', KnowledgeBtn> = {
居中构图: {
key: '1',
name: '居中构图',
btnImage: 'shooting/居中构图底板',
desc: '居中构图就是主体放在画面中心,让画面更稳定主体更突出,拍摄时需避免选取杂乱的背景,可以用大光圈或者长焦距让背景虚化使主体从背景中跳出来适合拍摄单个主体,操作比较简单,非常适合新手使用',
images: ['shooting/居中构图1', 'shooting/居中构图2']
},
三分法构图: {
key: '2',
name: '三分法构图',
btnImage: 'shooting/三分法构图底板',
desc: '三分法构图又称九宫格构图,就是把拍摄主体置于九宫格的任意一条三分位置,三分构图法是比较常见的构图法可以视觉上让整体画面更加生动,使用三分构图法拍摄人像时,尽量占据画面的下2格比例和重心会更协调',
images: ['shooting/图片待定1', 'shooting/图片待定2']
},
对角线构图: {
key: '3',
name: '对角线构图',
btnImage: 'shooting/对角线构图底板',
desc: '对角线构图属于引导线构图的一种,所以也是借助引导线的视觉引导效果来突出强调主体。另外对角线构图在画面中制造出对角线的走向即可。',
images: ['shooting/对角线构图1', 'shooting/对角线构图2']
}
}
const openKnowledge = (name: '三分法构图' | '居中构图' | '对角线构图') => {
currentKnowledge.value = knowledgeMap[name]
showKnowledgeCard.value = true
}
const currentCompositionKey = computed(() => {
const k = currentKnowledge.value?.key
if (k === '1' || k === '2' || k === '3') return k
return '1'
})
// 图标点击(基于 hook 的意图判断)
const handleIconClick = (icon: string) => {
const action = getIconAction(icon)
if (!action) return
if (action.type === 'navigate') {
router.push(action.payload)
return
}
if (action.type === 'expand') {
showSubIcons.value = true
selectedPrimaryIcon.value = 'skill/画面构图选中'
return
}
if (action.type === 'openKnowledge') {
openKnowledge(action.payload.name)
}
}
</script>
<template>
<div class="w-full h-full relative font-semibold">
<!-- 背景图片-center -->
<div
class="absolute inset-0 bg-cover bg-center bg-no-repeat"
:style="{ backgroundImage: getBackgroundImage('skill/技能树背景') }"
></div>
<ModuleSwitcher :module-name="currentModule.name" @prev="onPrevModule" @next="onNextModule" />
<!-- 周围模块标签(静态绘制) -->
<CategoryTags
:status="status"
:side-status="sideStatus"
:selected-category="selectedCategory"
@select="onCategorySelect"
/>
<!-- 动态显示分类对应的icon -->
<IconLayer
v-if="currentCategoryConfig"
:positions="currentCategoryConfig.positions"
:displayed-icons="displayedIcons"
:side-status="sideStatus"
@iconClick="handleIconClick"
/>
<!-- 返回按钮 -->
<ImgButton
class="absolute right-0 top-0"
:image="IMAGE_PATHS.COMPONENTS.BACK"
:title="backButtonTooltip"
@click="handleClose"
/>
<!-- 树 -->
<div
class="absolute top-28 left-[calc(50%-30px)] -translate-x-1/2 w-[786px] h-[875px] bg-cover bg-center bg-no-repeat"
:style="{
backgroundImage: status
? getBackgroundImage('skill/技能树-亮')
: getBackgroundImage('skill/技能树-灰')
}"
></div>
<div class="absolute bottom-10 left-1/2 -translate-x-1/2">
<span class="red-text text-4xl">{{
!status ? '当前尚未收集任何知识点,快去学习收集知识点吧' : '当前已全部解锁'
}}</span>
</div>
<!-- 引导与高亮 -->
<GuideSteps
v-if="status"
:steps="steps"
:highlight-selector="currentHighlight"
:highlight-padding="12"
:highlight-radius="20"
@step-change="handleStepChange"
position-offset-y="85%"
/>
<!-- 知识卡片 -->
<KnowledgeCard
v-if="currentKnowledge"
:visible="showKnowledgeCard"
:title="currentKnowledge.name"
:desc="currentKnowledge.desc"
:images="currentKnowledge.images"
:compositionKey="currentCompositionKey"
@close="showKnowledgeCard = false"
/>
</div>
</template>
<style scoped>
/* 强发光蓝绿主标题(图2风格) */
.blue-text {
/* 文字本体采用蓝绿渐变并保持白色高亮视觉 */
background: linear-gradient(180deg, #cfffff 0%, #9ff3ff 40%, #6ee7ff 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
/* 多层发光,柔边外扩 */
text-shadow:
0 0 2px rgba(255, 255, 255, 0.9),
0 0 6px rgba(79, 241, 255, 0.9),
0 0 14px rgba(79, 241, 255, 0.8),
0 0 28px rgba(0, 199, 255, 0.6),
0 0 46px rgba(0, 199, 255, 0.45);
}
/* 强发光白粉副标题(图2风格) */
.red-text {
background: linear-gradient(180deg, #ffffff 0%, #ffe9e9 55%, #ffd1d1 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow:
0 0 2px rgba(255, 255, 255, 0.9),
0 0 8px rgba(255, 135, 135, 0.85),
0 0 18px rgba(255, 135, 135, 0.65),
0 0 32px rgba(255, 110, 110, 0.55);
}
/* 模块标签基础样式 */
.module {
position: absolute;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
}
.module:hover {
transform: scale(1.05);
filter: brightness(1.2);
}
/* 竖排布局 */
.module-vertical {
cursor: pointer;
align-items: flex-end;
}
.module-vertical .module-text {
writing-mode: vertical-rl;
text-orientation: upright;
}
.module-vertical .module-percent {
transform: translateY(6px);
}
.module-text {
/* color: rgba(150, 150, 150, 0.9); */
font-size: 36px;
letter-spacing: 8px;
}
.module-percent {
color: #ffffff;
font-size: 28px;
}
/* 位置近似图2布局 */
.module-left-bottom {
left: 10%;
bottom: 22%;
}
.module-left-middle {
left: 25%;
top: 48%;
}
.module-right-middle {
right: 26%;
top: 52%;
}
.module-right-upper {
right: 18%;
top: 42%;
}
.module-right-far {
right: 8%;
top: 50%;
}
</style>