更新依赖项及历史人物相关内容资源

- 更新 package.json 和 package-lock.json 中的依赖项
- 新增/更新陈云、黄炎培、鲁迅、孙中山的音频文件
- 新增场景和组件的图片资源
- 清理未使用的音频和图片文件
- 优化多个组件和页面的实现逻辑
This commit is contained in:
zhongyidian committed 2026-04-20 13:27:18 +08:00
1 parent b1d55b9464
commit e0fa47d21a
629 files changed
+24390 -2991

No files matched your search

+233
View File
@@ -0,0 +1,233 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { Image } from '@/components'
import { cn, getBackgroundImage, getStaticImage } from '@/lib/utils'
import { useNavigate, useParams } from 'react-router-dom'
import { saveZhanglepingStore, useZhanglepingStore } from '@/store/zhanglepingStore'
import {
type BackgroundKey,
type ClothesKey,
type MaterialKey,
type SidebarBlockKey,
STAR_NAME,
backgroundToAllowedMaterials,
buildCompositeDataUrl,
getBackgroundAssetKey,
materialConflicts,
materialOptions,
sidebarBlocks,
} from './draw1.config'
import { TopMenuPanel } from './TopMenuPanel'
const ZhanglepingDraw1 = () => {
const navigate = useNavigate()
const { moduleName } = useParams<{ moduleName: string }>()
const { setDrawArtwork } = useZhanglepingStore()
const [activeBlock, setActiveBlock] = useState<SidebarBlockKey>('衣柜')
const [selectedClothes, setSelectedClothes] = useState<ClothesKey>('默认')
const [selectedBackground, setSelectedBackground] = useState<BackgroundKey>('城际高铁')
const [selectedMaterials, setSelectedMaterials] = useState<Set<MaterialKey>>(new Set())
const [showCaptionInput, setShowCaptionInput] = useState(false)
const [captionText, setCaptionText] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const captionInputRef = useRef<HTMLInputElement>(null)
const compositionRef = useRef<HTMLDivElement>(null)
const allowedMaterialKeys = useMemo(
() => backgroundToAllowedMaterials[selectedBackground] ?? [],
[selectedBackground]
)
const backgroundAssetKey = getBackgroundAssetKey(selectedBackground)
const scenePreviewSrc = `${STAR_NAME}/reflection/draw/布景/${backgroundAssetKey}`
const clothingSrc = `${STAR_NAME}/reflection/draw/衣柜/${selectedClothes}`
const boardBgSrc = `${STAR_NAME}/reflection/draw/画板底图`
// 切换布景后:移除不允许的素材
useEffect(() => {
setSelectedMaterials((prev) => {
const next = new Set([...prev].filter((k) => allowedMaterialKeys.includes(k)))
return next.size === prev.size ? prev : next
})
}, [allowedMaterialKeys])
useEffect(() => {
if (showCaptionInput) captionInputRef.current?.focus()
}, [showCaptionInput])
const handleToggleMaterial = (key: MaterialKey) => {
setSelectedMaterials((prev) => {
const next = new Set(prev)
if (next.has(key)) {
next.delete(key)
return next
}
const conflicts = materialConflicts[key] ?? []
for (const c of conflicts) next.delete(c)
if (next.size < 3) next.add(key)
return next
})
}
const handleSubmitArtwork = async () => {
if (!showCaptionInput) {
setShowCaptionInput(true)
return
}
if (isSubmitting) return
setIsSubmitting(true)
try {
const dataUrl = await buildCompositeDataUrl(compositionRef.current!)
setDrawArtwork(dataUrl, captionText.trim())
saveZhanglepingStore()
navigate(`/${moduleName}/${STAR_NAME}/reflection5`)
} catch (error) {
console.error('Failed to submit draw artwork', error)
} finally {
setIsSubmitting(false)
}
}
const isPreviewVisible = activeBlock !== '任务提示'
if (!moduleName) return null
return (
<div
className="w-full h-full relative overflow-hidden bg-cover bg-center bg-no-repeat"
style={{ backgroundImage: getBackgroundImage(boardBgSrc) }}
>
{/* 中央预览区 */}
<div
ref={compositionRef}
className="absolute left-20 bottom-[86px] w-[1605px] h-[904px] bg-cover bg-center bg-no-repeat"
style={{ backgroundImage: getBackgroundImage(scenePreviewSrc) }}
>
<div className="relative w-full h-full">
{isPreviewVisible && (
<div className="absolute left-111 bottom-0 z-10">
<Image src={getStaticImage(clothingSrc)} alt="衣柜选项" />
</div>
)}
{isPreviewVisible &&
materialOptions
.filter((opt) => selectedMaterials.has(opt.key))
.map((opt) => (
<div
key={opt.key}
className={cn('absolute', opt.key === '车' ? 'z-8' : 'z-20')}
data-material-key={opt.key}
style={{
top: opt.position.top,
left: opt.position.left,
bottom: opt.position.bottom,
right: opt.position.right,
}}
>
<Image src={getStaticImage(opt.assetSrc)} alt={opt.key} />
</div>
))}
</div>
</div>
{/* 顶部菜单 */}
<div className="absolute left-0 top-4 z-30 w-full">
<TopMenuPanel
activeBlock={activeBlock}
selectedClothes={selectedClothes}
selectedBackground={selectedBackground}
selectedMaterials={selectedMaterials}
allowedMaterialKeys={allowedMaterialKeys}
onSelectClothes={setSelectedClothes}
onSelectBackground={setSelectedBackground}
onToggleMaterial={handleToggleMaterial}
/>
</div>
{/* 任务提示窗口 */}
{activeBlock === '任务提示' && (
<div
className="absolute left-8 top-[26%] z-30 w-[1724px] h-[424px] bg-no-repeat"
style={{
backgroundImage: getBackgroundImage(`${STAR_NAME}/reflection/draw/创作任务面板`),
backgroundSize: '100% 100%',
}}
>
<div className="flex items-center justify-center pt-28">
<div className="w-full text-center text-[#8B6914] font-medium leading-tight">
<div className="text-3xl space-y-8">
<div>①为三毛换装</div>
<div>②搭配1个新时代场景</div>
<div>③融入1-3个新时代元素</div>
<div>④配1句简短正能量文字</div>
</div>
</div>
</div>
</div>
)}
{/* 右侧侧边栏 */}
<div className="absolute right-[4px] top-[162px] z-40 w-[162px]">
<div className="flex flex-col gap-4">
{sidebarBlocks.map((blk) => (
<button key={blk.key} type="button" onClick={() => setActiveBlock(blk.key)}>
<Image
className={cn(activeBlock === blk.key ? 'opacity-100' : 'opacity-60')}
src={getStaticImage(blk.iconSrc)}
alt={blk.key}
/>
</button>
))}
</div>
</div>
{/* 底部输入框 */}
{showCaptionInput && (
<div
className="absolute left-20 bottom-2.5 bg-cover bg-center bg-no-repeat w-[1724px] h-[100px] z-40"
style={{ backgroundImage: getBackgroundImage(`${STAR_NAME}/reflection/draw/写字底板`) }}
>
<div className="flex items-center justify-center">
<input
ref={captionInputRef}
type="text"
id="textInput"
name="textInput"
maxLength={15}
value={captionText}
onChange={(e) => setCaptionText(Array.from(e.target.value).slice(0, 15).join(''))}
className={cn(
'w-200 h-24 bg-transparent',
'text-4xl! text-[#8B6914] leading-none placeholder:text-3xl!'
)}
autoComplete="off"
placeholder="提交之前,请给你创作的我配一句简短的正能量文字吧!"
/>
</div>
</div>
)}
{/* 底部按钮 */}
<div className="absolute -left-10 bottom-0 z-50">
<Image
src={getStaticImage(`${STAR_NAME}/reflection/draw/返回`)}
button
onClick={() => navigate(`/${moduleName}/${STAR_NAME}/draw`)}
/>
</div>
<div className="absolute -right-10 bottom-0 z-50">
<Image
src={getStaticImage(`${STAR_NAME}/reflection/draw/提交作品`)}
button
className={isSubmitting ? 'opacity-70 pointer-events-none' : undefined}
onClick={handleSubmitArtwork}
/>
</div>
</div>
)
}
export default ZhanglepingDraw1