chore: 更新素材和组件优化
- 更新展品图片(鲁迅/毛泽东/孙中山/黄炎培/聂耳/张乐平故居) - 新增点击查看图标、报纸素材 - 优化视频组件、拼图组件、展品组件 - 更新陈云音频资源 - 新增输入验证文档和工具函数 - 组件布局和动画优化 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
73d02eb62d
commit
73f6df92ce
130 files changed
+2345
-759
No files matched your search
@@ -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>
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user