feat: add puzzle residence configuration and reflection progress logic
- Introduced `puzzleResidences.ts` to define puzzle residence configurations including properties like display name, position, and puzzle availability. - Added `reflectionProgress.ts` to manage badge unlocking logic based on place completion, including functions to determine unlocked badges and video sources for scenes.
This commit is contained in:
1 parent
e0fa47d21a
commit
f5f36f75b5
153 files changed
+3846
-2352
No files matched your search
@@ -29,7 +29,7 @@ const ExhibitDescriptionNarration = ({
|
||||
visible = true,
|
||||
autoPlay = true,
|
||||
audioSrc,
|
||||
audioChannelId,
|
||||
audioChannelId = 'exhibit-description-narration',
|
||||
className,
|
||||
}: ExhibitDescriptionNarrationProps) => {
|
||||
// 音频管理:默认全局互斥,如果传了 audioChannelId 则按 channel 互斥
|
||||
@@ -89,7 +89,7 @@ const ExhibitDescriptionNarration = ({
|
||||
/>
|
||||
<div className="relative z-10 flex flex-col gap-4">
|
||||
{/* 文本内容 */}
|
||||
<div className="flex-1 text-white text-2xl leading-12 tracking-wide font-medium">
|
||||
<div className="flex-1 text-white text-2xl leading-12 tracking-wide font-medium indent-12">
|
||||
{text}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { cn, getStaticImage } from '@/lib/utils'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { type ReactNode, useEffect, useMemo, useState } from 'react'
|
||||
import { FlipActionButton, Image } from '@/components'
|
||||
import { motion } from 'framer-motion'
|
||||
|
||||
@@ -10,6 +10,14 @@ const OVERLAY_STYLE: React.CSSProperties = {
|
||||
pointerEvents: 'none',
|
||||
}
|
||||
|
||||
const isDirectImageSrc = (src: string) =>
|
||||
src.startsWith('http://') ||
|
||||
src.startsWith('https://') ||
|
||||
src.startsWith('/') ||
|
||||
src.startsWith('data:image/')
|
||||
|
||||
const resolveImageSrc = (src: string) => (isDirectImageSrc(src) ? src : getStaticImage(src))
|
||||
|
||||
export interface ExhibitFlipOverlayProps {
|
||||
/** 展品正面图片地址,可以是静态资源路径或完整 URL */
|
||||
src: string
|
||||
@@ -29,6 +37,10 @@ export interface ExhibitFlipOverlayProps {
|
||||
visible?: boolean
|
||||
/** 额外的自定义类 */
|
||||
className?: string
|
||||
/** 底部操作区 */
|
||||
footer?: ReactNode
|
||||
/** 底部操作区额外类名 */
|
||||
footerClassName?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,6 +57,8 @@ const ExhibitFlipOverlay = ({
|
||||
onClose,
|
||||
visible = true,
|
||||
className,
|
||||
footer,
|
||||
footerClassName,
|
||||
}: ExhibitFlipOverlayProps) => {
|
||||
const backSrc = getStaticImage('components/close')
|
||||
|
||||
@@ -63,18 +77,11 @@ const ExhibitFlipOverlay = ({
|
||||
}
|
||||
}, [visible, src, flipSrc])
|
||||
|
||||
const frontImageSrc =
|
||||
src.startsWith('http://') || src.startsWith('https://') || src.startsWith('/')
|
||||
? src
|
||||
: getStaticImage(src)
|
||||
const frontImageSrc = resolveImageSrc(src)
|
||||
|
||||
const backImageSrc = useMemo(() => {
|
||||
if (!flipSrc) return null
|
||||
return flipSrc.startsWith('http://') ||
|
||||
flipSrc.startsWith('https://') ||
|
||||
flipSrc.startsWith('/')
|
||||
? flipSrc
|
||||
: getStaticImage(flipSrc)
|
||||
return resolveImageSrc(flipSrc)
|
||||
}, [flipSrc])
|
||||
|
||||
const canFlip = Boolean(backImageSrc && backImageSrc !== frontImageSrc)
|
||||
@@ -200,6 +207,20 @@ const ExhibitFlipOverlay = ({
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showChrome && footer && (
|
||||
<motion.div
|
||||
className={cn(
|
||||
'absolute left-1/2 bottom-38 z-30 -translate-x-1/2 pointer-events-auto',
|
||||
footerClassName
|
||||
)}
|
||||
initial={{ opacity: 0, y: 8 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.22, ease: 'easeOut' }}
|
||||
>
|
||||
{footer}
|
||||
</motion.div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user