forked from zhongyidian/super_video
feat: 完善技能树功能架构与逻辑流程文档,新增视频播放与学习进度追踪功能
This commit is contained in:
1 parent
6090abf955
commit
e9a1fee27f
42 files changed
+1849
-760
No files matched your search
@@ -6,6 +6,7 @@ import {
|
||||
getSubTagsByTag,
|
||||
getTagResourceCount
|
||||
} from '../resourceData'
|
||||
import { getStaticImage, getTagColor } from '@/utils'
|
||||
|
||||
interface Props {
|
||||
selectedModuleId: string | null
|
||||
@@ -32,6 +33,54 @@ const displayedModules = computed(() => {
|
||||
return allModules.value
|
||||
})
|
||||
|
||||
// 获取所有要显示的标签(按标签名称去重,并按固定顺序排列)
|
||||
const displayedTags = computed(() => {
|
||||
const tagMap = new Map<string, { moduleId: string; tagId: string; tagName: string }>()
|
||||
|
||||
displayedModules.value.forEach(module => {
|
||||
const tags = getTagsByModule(module.id)
|
||||
tags.forEach(tag => {
|
||||
// 使用标签名称作为唯一键,避免同名标签重复显示
|
||||
if (!tagMap.has(tag.name)) {
|
||||
tagMap.set(tag.name, {
|
||||
moduleId: module.id,
|
||||
tagId: tag.id,
|
||||
tagName: tag.name
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 定义固定的标签顺序(与位置数组对应)
|
||||
const tagOrder = [
|
||||
'视觉素养',
|
||||
'摄影基础',
|
||||
'剪映基础操作',
|
||||
'内容策划',
|
||||
'拍摄执行',
|
||||
'后期制作',
|
||||
'AI与版权伦理知识',
|
||||
'思路提点与案例赏析'
|
||||
]
|
||||
|
||||
// 按照固定顺序返回标签
|
||||
const orderedTags: Array<{ moduleId: string; tagId: string; tagName: string }> = []
|
||||
tagOrder.forEach(tagName => {
|
||||
if (tagMap.has(tagName)) {
|
||||
orderedTags.push(tagMap.get(tagName)!)
|
||||
}
|
||||
})
|
||||
|
||||
// 添加任何不在固定顺序中的标签
|
||||
tagMap.forEach((tag, tagName) => {
|
||||
if (!tagOrder.includes(tagName)) {
|
||||
orderedTags.push(tag)
|
||||
}
|
||||
})
|
||||
|
||||
return orderedTags
|
||||
})
|
||||
|
||||
// 检查标签是否展开
|
||||
const isTagExpanded = (tagId: string) => {
|
||||
return props.expandedTags.has(tagId)
|
||||
@@ -39,42 +88,57 @@ const isTagExpanded = (tagId: string) => {
|
||||
|
||||
// 检查标签是否选中
|
||||
const isTagSelected = (moduleId: string, tagId: string) => {
|
||||
return props.selectedModuleId === moduleId && props.selectedTagId === tagId
|
||||
if (props.selectedModuleId) {
|
||||
// 模块模式:需要模块和标签都匹配
|
||||
return props.selectedModuleId === moduleId && props.selectedTagId === tagId
|
||||
} else {
|
||||
// 总览模式:只需要标签匹配(因为总览模式下 selectedModuleId 为 null)
|
||||
return props.selectedTagId === tagId
|
||||
}
|
||||
}
|
||||
|
||||
// 检查子标签是否选中
|
||||
const isSubTagSelected = (moduleId: string, tagId: string, subTagId: string) => {
|
||||
return (
|
||||
props.selectedModuleId === moduleId &&
|
||||
props.selectedTagId === tagId &&
|
||||
props.selectedSubTagId === subTagId
|
||||
)
|
||||
if (props.selectedModuleId) {
|
||||
// 模块模式:需要模块、标签和子标签都匹配
|
||||
return (
|
||||
props.selectedModuleId === moduleId &&
|
||||
props.selectedTagId === tagId &&
|
||||
props.selectedSubTagId === subTagId
|
||||
)
|
||||
} else {
|
||||
// 总览模式:只需要标签和子标签匹配
|
||||
return props.selectedTagId === tagId && props.selectedSubTagId === subTagId
|
||||
}
|
||||
}
|
||||
|
||||
// 获取标签的资源数量
|
||||
const getResourceCount = (moduleId: string, tagId: string) => {
|
||||
return getTagResourceCount(moduleId, tagId)
|
||||
// 获取标签的资源数量(如果显示所有模块,则合并同名标签的数量)
|
||||
const getResourceCount = (moduleId: string, tagId: string, tagName: string) => {
|
||||
if (props.selectedModuleId) {
|
||||
// 如果选中了模块,只计算该模块的资源数量
|
||||
return getTagResourceCount(moduleId, tagId)
|
||||
} else {
|
||||
// 如果显示所有模块,计算所有模块中同名标签的资源总数
|
||||
let totalCount = 0
|
||||
displayedModules.value.forEach(module => {
|
||||
const tags = getTagsByModule(module.id)
|
||||
tags.forEach(tag => {
|
||||
if (tag.name === tagName) {
|
||||
totalCount += getTagResourceCount(module.id, tag.id)
|
||||
}
|
||||
})
|
||||
})
|
||||
return totalCount
|
||||
}
|
||||
}
|
||||
|
||||
// 获取标签颜色类 - 根据标签类型返回不同的颜色
|
||||
// 获取标签颜色类 - 根据标签类型返回不同的颜色(匹配目标效果图)
|
||||
const getTagColorClass = (tagName: string, isSelected: boolean) => {
|
||||
if (isSelected) {
|
||||
return 'bg-orange-500 border-white text-white'
|
||||
return getTagColor(tagName) + ' brightness-125'
|
||||
}
|
||||
|
||||
// 根据标签名称返回不同的颜色
|
||||
const colorMap: Record<string, string> = {
|
||||
后期制作: 'bg-blue-400 border-blue-300 text-white',
|
||||
拍摄执行: 'bg-green-400 border-green-300 text-white',
|
||||
内容策划: 'bg-orange-400 border-orange-300 text-white',
|
||||
视觉素养: 'bg-orange-400 border-orange-300 text-white',
|
||||
摄影基础: 'bg-blue-400 border-blue-300 text-white',
|
||||
剪映基础操作: 'bg-purple-400 border-purple-300 text-white',
|
||||
AI与版权伦理知识: 'bg-blue-400 border-blue-300 text-white',
|
||||
思路提点与案例赏析: 'bg-blue-400 border-blue-300 text-white'
|
||||
}
|
||||
|
||||
return colorMap[tagName] || 'bg-blue-400 border-blue-300 text-white'
|
||||
return getTagColor(tagName)
|
||||
}
|
||||
|
||||
const handleTagClick = (moduleId: string, tagId: string) => {
|
||||
@@ -85,166 +149,206 @@ const handleSubTagClick = (moduleId: string, tagId: string, subTagId: string) =>
|
||||
emit('subTagClick', moduleId, tagId, subTagId)
|
||||
}
|
||||
|
||||
// 计算标签节点的位置 - 使用更自然的树形分布
|
||||
const getTagPosition = (
|
||||
moduleIndex: number,
|
||||
tagIndex: number,
|
||||
totalTags: number,
|
||||
moduleId: string
|
||||
) => {
|
||||
// 计算所有标签的总数(用于全局定位)
|
||||
const modules = displayedModules.value
|
||||
let globalTagIndex = 0
|
||||
for (let i = 0; i < moduleIndex; i++) {
|
||||
globalTagIndex += getTagsByModule(modules[i].id).length
|
||||
// 获取子标签的图标
|
||||
const getSubTagIcon = (subTagName: string) => {
|
||||
// 图标文件名映射
|
||||
const iconMap: Record<string, string> = {
|
||||
构图: 'skill/icon/构图.png',
|
||||
光线: 'skill/icon/光线.png',
|
||||
运镜: 'skill/icon/运镜.png',
|
||||
设备准备: 'skill/icon/设备准备.png',
|
||||
脚本制作: 'skill/icon/脚本与分镜制作.png',
|
||||
光线运用: 'skill/icon/光线运用.png',
|
||||
运镜技巧: 'skill/icon/运镜技巧.png',
|
||||
选题与构思: 'skill/icon/选题与构思.png',
|
||||
脚本与分镜制作: 'skill/icon/脚本与分镜制作.png',
|
||||
拍摄技巧: 'skill/icon/拍摄技巧.png',
|
||||
剪辑技巧: 'skill/icon/剪辑技巧.png',
|
||||
包装: 'skill/icon/包装.png',
|
||||
调色: 'skill/icon/调色.png',
|
||||
音效: 'skill/icon/音效.png',
|
||||
素材管理: 'skill/icon/素材管理.png',
|
||||
蒙太奇剪辑: 'skill/icon/蒙太奇剪辑.png',
|
||||
版权意识: 'skill/icon/版权意识.png',
|
||||
ai技术应用: 'skill/icon/ai技术应用.png',
|
||||
思路提点: 'skill/icon/思路提点.png',
|
||||
案例合集: 'skill/icon/案例合集.png'
|
||||
}
|
||||
globalTagIndex += tagIndex
|
||||
return iconMap[subTagName] || 'skill/icon/构图.png' // 默认图标
|
||||
}
|
||||
|
||||
// 计算总标签数
|
||||
const totalAllTags = modules.reduce((sum, m) => sum + getTagsByModule(m.id).length, 0)
|
||||
// 获取技能树标题
|
||||
const getTreeTitle = () => {
|
||||
if (!props.selectedModuleId) {
|
||||
return '技能树总览'
|
||||
}
|
||||
const module = allModules.value.find(m => m.id === props.selectedModuleId)
|
||||
return module?.name || '技能树总览'
|
||||
}
|
||||
|
||||
// 垂直位置:从树根向上分布,形成树形结构
|
||||
const baseTop = 85 // 树根位置(底部)
|
||||
const maxTop = 15 // 最顶部位置
|
||||
const verticalRange = baseTop - maxTop
|
||||
// 计算标签节点的位置 - 根据目标效果图1的布局
|
||||
const getTagPosition = (tagIndex: number) => {
|
||||
// 定义所有标签的具体位置(根据目标效果图1)
|
||||
// 标签顺序:视觉素养、摄影基础、剪映基础操作、内容策划、拍摄执行、后期制作、AI与版权伦理知识、思路提点与案例赏析
|
||||
const positions = [
|
||||
{ top: 38, left: 33 }, // 0: 视觉素养 - 顶部左侧
|
||||
{ top: 51, left: 10 }, // 1: 摄影基础 - 顶部右侧偏左
|
||||
{ top: 51, left: 58 }, // 2: 剪映基础操作 - 顶部右侧
|
||||
{ top: 32, left: 4 }, // 3: 内容策划 - 中部左侧
|
||||
{ top: 18, left: 14 }, // 4: 拍摄执行 - 中部右侧(部分在外)
|
||||
{ top: 5, left: 35 }, // 5: 后期制作 - 中下部左侧
|
||||
{ top: 22, left: 48 }, // 6: AI与版权伦理知识 - 中下部中间
|
||||
{ top: 35, left: 64 } // 7: 思路提点与案例赏析 - 中下部右侧
|
||||
]
|
||||
|
||||
// 根据全局索引计算垂直位置,形成扇形分布
|
||||
const verticalRatio = totalAllTags > 1 ? globalTagIndex / (totalAllTags - 1) : 0
|
||||
const top = baseTop - verticalRange * verticalRatio * 0.75 // 使用0.75让标签更集中在树的上半部分
|
||||
|
||||
// 水平位置:左右交替分布,形成树形
|
||||
const isLeft = globalTagIndex % 2 === 0
|
||||
const horizontalSpread = 28 // 水平展开范围
|
||||
const horizontalOffset = isLeft ? -horizontalSpread : horizontalSpread
|
||||
|
||||
// 根据垂直位置调整水平偏移,形成扇形(越往上,水平偏移越小)
|
||||
const verticalFactor = Math.max(0, (top - baseTop) / (maxTop - baseTop))
|
||||
const left = 50 + horizontalOffset * (1 - verticalFactor * 0.4)
|
||||
// 如果标签数量超过预定义位置,使用循环
|
||||
const position = positions[tagIndex % positions.length]
|
||||
|
||||
return {
|
||||
top: `${Math.max(maxTop, Math.min(baseTop, top))}%`,
|
||||
left: `${Math.max(5, Math.min(95, left))}%`
|
||||
top: `${position.top}%`,
|
||||
left: `${position.left}%`
|
||||
}
|
||||
}
|
||||
|
||||
// 计算连接线的起点和终点
|
||||
const getConnectionLine = (
|
||||
moduleIndex: number,
|
||||
tagIndex: number,
|
||||
totalTags: number,
|
||||
moduleId: string
|
||||
) => {
|
||||
const tagPos = getTagPosition(moduleIndex, tagIndex, totalTags, moduleId)
|
||||
const rootX = 50 // 树根中心X位置
|
||||
const rootY = 92 // 树根中心Y位置(底部)
|
||||
// 计算子标签围绕父标签的位置(圆形分布)
|
||||
const getSubTagPosition = (subTagIndex: number, totalSubTags: number) => {
|
||||
// 圆形半径(单位:像素)- 根据子标签数量调整半径
|
||||
// 使用更大的半径确保子标签不会挤在一起
|
||||
const baseRadius = 160 // 基础半径180px
|
||||
const radius = totalSubTags <= 3 ? baseRadius : baseRadius + (totalSubTags - 3) * 30
|
||||
|
||||
// 计算角度:均匀分布在360度圆周上
|
||||
// 从顶部开始(-90度),顺时针分布
|
||||
const angleStep = (360 / totalSubTags) * (Math.PI / 180) // 转换为弧度
|
||||
const angle = -Math.PI / 2 + subTagIndex * angleStep // -90度开始
|
||||
|
||||
// 计算子标签的位置(相对于父标签中心,使用像素单位)
|
||||
const offsetX = radius * Math.cos(angle)
|
||||
const offsetY = radius * Math.sin(angle)
|
||||
|
||||
return {
|
||||
x1: rootX,
|
||||
y1: rootY,
|
||||
x2: parseFloat(tagPos.left),
|
||||
y2: parseFloat(tagPos.top) + 3 // 标签中心位置
|
||||
transform: `translate(calc(-50% + ${offsetX}px), calc(-50% + ${offsetY + 30}px))` // 让子标签以自身中心定位,并应用偏移
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full h-full overflow-hidden">
|
||||
<div class="relative w-full h-full select-none">
|
||||
<!-- 技能树背景容器 -->
|
||||
<div class="absolute inset-0">
|
||||
<!-- 树根节点 - 底部居中 -->
|
||||
<!-- 模块名称 - 底部居中 -->
|
||||
<div
|
||||
class="absolute bottom-4 left-1/2 -translate-x-1/2 px-8 py-4 bg-gradient-to-r from-blue-500/90 to-blue-400/90 border-2 border-blue-300 rounded-xl text-white text-2xl font-bold shadow-2xl shadow-blue-500/60 z-10"
|
||||
class="absolute bottom-4 left-1/2 -translate-x-1/2 text-white text-5xl font-bold [text-shadow:_0px_0px_20px_#3fcdd6,_0px_0px_20px_#3fcdd6,_0px_0px_20px_#3fcdd6,_0px_0px_20px_#3fcdd6] z-30"
|
||||
>
|
||||
技能树总览
|
||||
{{ getTreeTitle() }}
|
||||
</div>
|
||||
|
||||
<!-- 连接线容器 -->
|
||||
<svg class="absolute inset-0 pointer-events-none z-0" style="overflow: visible">
|
||||
<template v-for="(module, moduleIndex) in displayedModules" :key="`lines-${module.id}`">
|
||||
<template v-for="(tag, tagIndex) in getTagsByModule(module.id)" :key="`line-${tag.id}`">
|
||||
<line
|
||||
:x1="`${getConnectionLine(moduleIndex, tagIndex, getTagsByModule(module.id).length, module.id).x1}%`"
|
||||
:y1="`${getConnectionLine(moduleIndex, tagIndex, getTagsByModule(module.id).length, module.id).y1}%`"
|
||||
:x2="`${getConnectionLine(moduleIndex, tagIndex, getTagsByModule(module.id).length, module.id).x2}%`"
|
||||
:y2="`${getConnectionLine(moduleIndex, tagIndex, getTagsByModule(module.id).length, module.id).y2}%`"
|
||||
stroke="rgba(255, 255, 255, 0.4)"
|
||||
stroke-width="2"
|
||||
class="drop-shadow-sm"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
</svg>
|
||||
|
||||
<!-- 模块和标签节点 -->
|
||||
<template v-for="(module, moduleIndex) in displayedModules" :key="module.id">
|
||||
<template v-for="(tag, tagIndex) in getTagsByModule(module.id)" :key="tag.id">
|
||||
<!-- 标签节点 -->
|
||||
<div
|
||||
class="absolute z-10"
|
||||
:style="
|
||||
getTagPosition(moduleIndex, tagIndex, getTagsByModule(module.id).length, module.id)
|
||||
"
|
||||
>
|
||||
<!-- 资源标签节点 -->
|
||||
<div
|
||||
:class="[
|
||||
'relative px-4 py-2.5 rounded-lg border-2 cursor-pointer transition-all shadow-lg hover:scale-110 min-w-[140px] max-w-[180px]',
|
||||
getTagColorClass(tag.name, isTagSelected(module.id, tag.id)),
|
||||
isTagSelected(module.id, tag.id) ? 'ring-2 ring-orange-300 z-20' : ''
|
||||
]"
|
||||
@click="handleTagClick(module.id, tag.id)"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="text-sm font-semibold truncate">{{ tag.name }}</span>
|
||||
<div class="flex items-center gap-1.5 flex-shrink-0">
|
||||
<span
|
||||
class="flex items-center justify-center w-5 h-5 bg-white/30 rounded-full text-white text-xs font-bold"
|
||||
<template v-for="(tag, tagIndex) in displayedTags" :key="`${tag.moduleId}-${tag.tagId}`">
|
||||
<!-- 标签节点容器 - 选中的标签提高 z-index 显示在蒙版之上 -->
|
||||
<div
|
||||
class="absolute"
|
||||
:class="isTagSelected(tag.moduleId, tag.tagId) ? 'z-20' : 'z-[1]'"
|
||||
:style="getTagPosition(tagIndex)"
|
||||
>
|
||||
<!-- 资源标签节点 -->
|
||||
<div class="relative">
|
||||
<!-- 标签主体容器 - 用于精确对齐定位层 -->
|
||||
<div class="relative">
|
||||
<!-- 标签主体 -->
|
||||
<div
|
||||
:class="[
|
||||
'relative py-5 cursor-pointer min-w-[240px] max-w-[220px] transition-all hover:scale-105',
|
||||
'border-4 rounded-full shadow-lg',
|
||||
getTagColorClass(tag.tagName, isTagSelected(tag.moduleId, tag.tagId)),
|
||||
isTagSelected(tag.moduleId, tag.tagId) ? 'ring-2 ring-white/60' : ''
|
||||
]"
|
||||
@click="handleTagClick(tag.moduleId, tag.tagId)"
|
||||
>
|
||||
<div class="flex items-center justify-center gap-2 relative z-10">
|
||||
<span class="text-2xl font-bold truncate">{{ tag.tagName }}</span>
|
||||
<!-- 展开箭头 -->
|
||||
<!-- <span
|
||||
v-if="getSubTagsByTag(tag.moduleId, tag.tagId).length > 0"
|
||||
class="text-white text-xs flex-shrink-0"
|
||||
>
|
||||
{{ getResourceCount(module.id, tag.id) }}
|
||||
</span>
|
||||
<span
|
||||
v-if="getSubTagsByTag(module.id, tag.id).length > 0"
|
||||
class="text-white text-xs"
|
||||
{{ isTagExpanded(tag.tagId) ? '▼' : '▶' }}
|
||||
</span> -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 子标签定位层 - 使用 inset-0 与标签主体完全对齐(包括 border 和 padding) -->
|
||||
<div
|
||||
v-if="
|
||||
getSubTagsByTag(tag.moduleId, tag.tagId).length > 0 && isTagExpanded(tag.tagId)
|
||||
"
|
||||
class="absolute inset-0 pointer-events-none"
|
||||
>
|
||||
<!-- 子标签列表(围绕父标签圆形分布) -->
|
||||
<div
|
||||
v-for="(subTag, subTagIndex) in getSubTagsByTag(tag.moduleId, tag.tagId)"
|
||||
:key="subTag.id"
|
||||
class="absolute z-30 pointer-events-auto"
|
||||
style="top: 50%; left: 50%"
|
||||
:style="
|
||||
getSubTagPosition(subTagIndex, getSubTagsByTag(tag.moduleId, tag.tagId).length)
|
||||
"
|
||||
>
|
||||
<div
|
||||
:class="[
|
||||
'flex flex-col items-center justify-center cursor-pointer transition-all hover:scale-105'
|
||||
// isSubTagSelected(tag.moduleId, tag.tagId, subTag.id)
|
||||
// ? 'border-orange-400 ring-2 ring-orange-300'
|
||||
// : 'border-white/50'
|
||||
]"
|
||||
@click.stop="handleSubTagClick(tag.moduleId, tag.tagId, subTag.id)"
|
||||
>
|
||||
{{ isTagExpanded(tag.id) ? '▼' : '▶' }}
|
||||
</span>
|
||||
<!-- 图标-图标资源已经带了文字 -->
|
||||
<div class="w-30">
|
||||
<img :src="getStaticImage(getSubTagIcon(subTag.name))" :alt="subTag.name" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 子标签列表(展开时显示) -->
|
||||
<!-- 数字徽章 - 右上角外侧 -->
|
||||
<div
|
||||
v-if="getSubTagsByTag(module.id, tag.id).length > 0 && isTagExpanded(tag.id)"
|
||||
class="absolute top-full left-0 mt-2 space-y-1.5 z-30"
|
||||
class="absolute -top-4 -right-4 flex items-center justify-center w-12 h-12 bg-[#5287c1] rounded-full text-white text-3xl font-bold shadow-lg"
|
||||
>
|
||||
<div
|
||||
v-for="subTag in getSubTagsByTag(module.id, tag.id)"
|
||||
:key="subTag.id"
|
||||
:class="[
|
||||
'px-3 py-1.5 rounded-lg border-2 cursor-pointer transition-all shadow-md hover:scale-105 whitespace-nowrap',
|
||||
isSubTagSelected(module.id, tag.id, subTag.id)
|
||||
? 'bg-orange-400 border-white text-white ring-1 ring-orange-300'
|
||||
: 'bg-blue-300/80 border-blue-200 text-white'
|
||||
]"
|
||||
@click.stop="handleSubTagClick(module.id, tag.id, subTag.id)"
|
||||
>
|
||||
<span class="text-xs font-medium">{{ subTag.name }}</span>
|
||||
</div>
|
||||
{{ getResourceCount(tag.moduleId, tag.tagId, tag.tagName) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 右侧标签 -->
|
||||
<div class="absolute right-0 top-1/2 -translate-y-1/2 -translate-x-4 z-20">
|
||||
<!-- 点击之后的蒙版 - 使用径向渐变,从中心透明到边缘半透明黑色 -->
|
||||
<transition
|
||||
enter-active-class="transition-opacity duration-300 ease-out"
|
||||
leave-active-class="transition-opacity duration-300 ease-in"
|
||||
enter-from-class="opacity-0"
|
||||
enter-to-class="opacity-100"
|
||||
leave-from-class="opacity-100"
|
||||
leave-to-class="opacity-0"
|
||||
>
|
||||
<div
|
||||
class="writing-vertical text-white text-2xl font-bold bg-blue-500/40 px-3 py-6 rounded-lg border border-blue-300/60 shadow-lg"
|
||||
>
|
||||
<span class="inline-block mb-2">●</span>
|
||||
<span>技能树</span>
|
||||
</div>
|
||||
</div>
|
||||
v-if="props.selectedTagId"
|
||||
class="absolute inset-0 z-10 pointer-events-auto"
|
||||
style="
|
||||
background: radial-gradient(
|
||||
circle at center,
|
||||
rgba(0, 0, 0, 0.5) 0%,
|
||||
rgba(0, 0, 0, 0.4) 20%,
|
||||
rgba(0, 0, 0, 0.3) 40%,
|
||||
transparent 70%
|
||||
);
|
||||
"
|
||||
></div>
|
||||
</transition>
|
||||
|
||||
<!-- 右侧标签 -->
|
||||
<img class="absolute right-4 top-0 z-30" :src="getStaticImage('skill/技能树标题.png')" alt="" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in new issue
Block a user