chore: 更新素材和组件优化

- 更新展品图片(鲁迅/毛泽东/孙中山/黄炎培/聂耳/张乐平故居)
- 新增点击查看图标、报纸素材
- 优化视频组件、拼图组件、展品组件
- 更新陈云音频资源
- 新增输入验证文档和工具函数
- 组件布局和动画优化

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zhongyidianandClaude Opus 4.8 committed 2026-06-21 21:50:40 +08:00
1 parent 73d02eb62d
commit 73f6df92ce
130 files changed
+2345 -759

No files matched your search

+75 -4
View File
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useRef, useState } from 'react'
import { AnimatePresence, motion } from 'framer-motion'
import { getPublicPath, getStaticImage } from '@/lib/utils'
import { filterInput } from '@/lib/utils/sensitiveWords'
import { filterInput, validateInput, containsSensitiveWord } from '@/lib/utils/sensitiveWords'
import { Image, Select, Video, type VideoRef } from '@/components'
import { showHint } from '@/components/hint'
@@ -98,6 +98,7 @@ const LetterTemplate = ({
const [showReplyVideo, setShowReplyVideo] = useState(false)
const [showScene, setShowScene] = useState(false)
const [showReplyImage, setShowReplyImage] = useState(false)
const [isComposing, setIsComposing] = useState(false) // 追踪中文输入法状态
const sendVideoRef = useRef<VideoRef>(null)
const replyVideoRef = useRef<VideoRef>(null)
const audioRef = useRef<HTMLAudioElement | null>(null)
@@ -123,6 +124,14 @@ const LetterTemplate = ({
return false
}
// 验证落款姓名
const signatureValidation = validateInput(trimmedSignatureName)
if (!signatureValidation.valid) {
showHint(`落款姓名${signatureValidation.message}`)
setSignatureError(true)
return false
}
const nextErrors = validate
? validate(nextValues, letterContent)
: nextValues.map((value, index) => {
@@ -139,6 +148,15 @@ const LetterTemplate = ({
return true
}
// 验证输入内容(禁止英文、数字、特殊符号、敏感词)
if (field.sanitize !== false) {
const validation = validateInput(trimmedValue)
if (!validation.valid) {
showHint(validation.message)
return true
}
}
if (field.correctValue && trimmedValue !== field.correctValue) {
if (field.hints?.error) {
showHint(field.hints.error)
@@ -206,7 +224,23 @@ const LetterTemplate = ({
const updateBlank = (index: number, value: string) => {
const field = resolvedFields[index] ?? DEFAULT_TEXT_FIELD
const nextValue = field.sanitize === false ? value : filterInput(value)
// 中文输入法输入过程中不过滤,等输入完成后再过滤
const shouldFilter = field.sanitize !== false && !isComposing
const nextValue = shouldFilter ? filterInput(value) : value
// 检测是否有非法字符被过滤掉(提示用户)
if (shouldFilter && value !== nextValue && value.length > 0) {
// 有字符被过滤了,检查是哪种类型
if (/[a-zA-Z]/.test(value)) {
showHint('不能包含英文字母')
} else if (/\d/.test(value)) {
showHint('不能包含数字')
} else if (/[\p{P}\p{S}]/u.test(value)) {
showHint('不能包含标点符号')
}
}
const nextValues = [...blankValues]
nextValues[index] = nextValue
setBlankValues(nextValues)
@@ -287,7 +321,20 @@ const LetterTemplate = ({
}
const handleSignatureNameChange = (value: string) => {
const nextValue = filterInput(value)
// 中文输入法输入过程中不过滤,等输入完成后再过滤
const nextValue = isComposing ? value : filterInput(value)
// 检测是否有非法字符被过滤掉(提示用户)
if (!isComposing && value !== nextValue && value.length > 0) {
if (/[a-zA-Z]/.test(value)) {
showHint('不能包含英文字母')
} else if (/\d/.test(value)) {
showHint('不能包含数字')
} else if (/[\p{P}\p{S}]/u.test(value)) {
showHint('不能包含标点符号')
}
}
setSignatureName(nextValue)
const hasError = !nextValue.trim()
@@ -318,6 +365,12 @@ const LetterTemplate = ({
value={signatureName}
maxLength={12}
onChange={(e) => handleSignatureNameChange(e.target.value)}
onCompositionStart={() => setIsComposing(true)}
onCompositionEnd={(e) => {
setIsComposing(false)
// 输入法完成后立即过滤
handleSignatureNameChange(filterInput(e.currentTarget.value))
}}
/>
<span className="text-[26px] leading-[1.8]">{currentDate}</span>
</div>
@@ -393,15 +446,33 @@ const LetterTemplate = ({
return (
<span key={`part-${lineIndex}-${partIndex}`}>
{partIndex === 0 ? `${linePrefix}${part}` : part}
<style>{`.blank-input::placeholder { color: #999999; font-size: 26px; }`}</style>
<input
name={`blank-${currentIndex}`}
className={`inline-block h-[36px] mx-2 bg-transparent border-b-2 border-dashed text-center outline-none ${field.widthClassName || 'w-[100px]'} ${field.className || ''} ${blankErrors[currentIndex] ? 'border-red-500' : 'border-[#4e402e]'}`}
className={`blank-input inline-block h-[36px] mx-2 bg-transparent border-b-2 border-dashed text-center outline-none ${field.widthClassName || 'w-[100px]'} ${field.className || ''} ${blankErrors[currentIndex] ? 'border-red-500' : 'border-[#4e402e]'}`}
style={{ fontSize: field.fontSize ? `${field.fontSize}px` : '26px' }}
placeholder={field.placeholder || ''}
autoComplete="off"
value={fieldValue}
maxLength={field.maxLength ?? 10}
onChange={(e) => updateBlank(currentIndex, e.target.value)}
onBlur={() => handleInputBlur(currentIndex)}
onCompositionStart={() => setIsComposing(true)}
onCompositionEnd={(e) => {
setIsComposing(false)
// 输入法完成后立即过滤
const field = resolvedFields[currentIndex] ?? DEFAULT_TEXT_FIELD
if (field.sanitize !== false) {
const originalValue = e.currentTarget.value
const filteredValue = filterInput(originalValue)
if (containsSensitiveWord(originalValue)) {
showHint('内容包含敏感词,已替换为星号')
}
updateBlank(currentIndex, filteredValue)
}
}}
/>
</span>
)