feat: 更新组件和引导对话,增强用户体验
- 在多个组件中添加新的选择和填空功能,提升互动性。 - 更新引导对话文本,确保信息准确性和一致性。 - 引入新的选择组件,优化用户填写体验。 - 清理冗余代码,提升可维护性。
This commit is contained in:
1 parent
a8723ab4aa
commit
5bdfdadede
27 files changed
+880
-1252
No files matched your search
@@ -140,7 +140,7 @@ const LetterToChenyun = ({
|
||||
textOrientation: "upright",
|
||||
}}
|
||||
>
|
||||
寄给陈云爷爷的信
|
||||
寄给陈云的信
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { getStaticImage } from "@/lib/utils";
|
||||
import { Image } from "@/components";
|
||||
import { Image, Select } from "@/components";
|
||||
import { filterInput } from "@/lib/utils/sensitiveWords";
|
||||
|
||||
export interface LetterToHuangyanpeiProps {
|
||||
/** 是否可见 */
|
||||
@@ -23,13 +24,71 @@ const LetterToHuangyanpei = ({
|
||||
}: LetterToHuangyanpeiProps) => {
|
||||
// 下划线可填写内容
|
||||
const [blankValues, setBlankValues] = useState<string[]>([]);
|
||||
// 每个空的校验状态:true 表示该空当前不符合要求(为空或超过 10 个字)
|
||||
// 每个空的校验状态:true 表示该空当前不符合要求
|
||||
const [blankErrors, setBlankErrors] = useState<boolean[]>([]);
|
||||
|
||||
// 是否已提交:提交后隐藏编辑信件,展示只读信件
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
|
||||
// 校验输入:不能为空、每个不超过 10 个字符
|
||||
// 用于跟踪是否已经自动填入过答案,避免重复填入
|
||||
const autoFilledRef = useRef(false);
|
||||
|
||||
// 填空类型定义:0=填空1(限填),1=填空2(任填),2=填空3(任填),3=填空4(限填)
|
||||
const BLANK_1_ANSWER = "手脑并用";
|
||||
const BLANK_4_ANSWER = "有业者乐业";
|
||||
|
||||
// 填空1的选项
|
||||
const BLANK_1_OPTIONS = [
|
||||
"手脑并用",
|
||||
// "知行合一",
|
||||
// "学以致用",
|
||||
// "理论实践"
|
||||
];
|
||||
|
||||
// 填空4的选项
|
||||
const BLANK_4_OPTIONS = [
|
||||
"有业者乐业",
|
||||
// "有业者敬业",
|
||||
// "有业者精业",
|
||||
// "有业者成业",
|
||||
];
|
||||
|
||||
// 自动浮现答案:填空1、4空白时自动填入正确答案
|
||||
useEffect(() => {
|
||||
// 只在组件可见且未自动填入过时检查
|
||||
if (!visible) {
|
||||
autoFilledRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (autoFilledRef.current) return;
|
||||
|
||||
setBlankValues((prevValues) => {
|
||||
const nextValues = [...prevValues];
|
||||
let changed = false;
|
||||
|
||||
// 填空1(索引0):如果为空,自动填入正确答案
|
||||
if (!nextValues[0] || nextValues[0].trim() === "") {
|
||||
nextValues[0] = BLANK_1_ANSWER;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
// 填空4(索引3):如果为空,自动填入正确答案
|
||||
if (!nextValues[3] || nextValues[3].trim() === "") {
|
||||
nextValues[3] = BLANK_4_ANSWER;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed || nextValues.length > 0) {
|
||||
// 如果已经有值或已更改,标记为已处理,避免重复检查
|
||||
autoFilledRef.current = true;
|
||||
}
|
||||
|
||||
return nextValues;
|
||||
});
|
||||
}, [visible]);
|
||||
|
||||
// 校验输入:确保所有填空符合标准
|
||||
const validateBlanks = () => {
|
||||
// 计算占位总数,防止有些没被输入过而是 undefined
|
||||
const placeholderCount = (letterContent.match(/____/g) || []).length;
|
||||
@@ -39,14 +98,30 @@ const LetterToHuangyanpei = ({
|
||||
let hasError = false;
|
||||
|
||||
for (let i = 0; i < placeholderCount; i++) {
|
||||
const raw = blankValues[i] || "";
|
||||
const raw = nextValues[i] || "";
|
||||
const v = raw.trim();
|
||||
|
||||
// 为空 或 超过 10 个字,都视为不符合要求:清空并标红
|
||||
if (!v || v.length > 10) {
|
||||
nextValues[i] = "";
|
||||
nextErrors[i] = true;
|
||||
hasError = true;
|
||||
if (i === 0) {
|
||||
// 填空1:限填,必须选择"手脑并用"
|
||||
if (!v || v !== BLANK_1_ANSWER) {
|
||||
nextValues[i] = "";
|
||||
nextErrors[i] = true;
|
||||
hasError = true;
|
||||
}
|
||||
} else if (i === 1 || i === 2) {
|
||||
// 填空2、3:任填,不能为空,不超过20个字
|
||||
if (!v || v.length > 20) {
|
||||
nextValues[i] = "";
|
||||
nextErrors[i] = true;
|
||||
hasError = true;
|
||||
}
|
||||
} else if (i === 3) {
|
||||
// 填空4:限填,必须选择"有业者乐业"
|
||||
if (!v || v !== BLANK_4_ANSWER) {
|
||||
nextValues[i] = "";
|
||||
nextErrors[i] = true;
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,6 +137,7 @@ const LetterToHuangyanpei = ({
|
||||
setSubmitted(false);
|
||||
setBlankValues([]);
|
||||
setBlankErrors([]);
|
||||
autoFilledRef.current = false; // 重置自动填入标记
|
||||
}
|
||||
}, [visible]);
|
||||
|
||||
@@ -187,32 +263,79 @@ const LetterToHuangyanpei = ({
|
||||
}
|
||||
|
||||
const currentIndex = placeholderIndex++;
|
||||
const isLimitedFill =
|
||||
currentIndex === 0 || currentIndex === 3; // 填空1和填空4为限填
|
||||
const options =
|
||||
currentIndex === 0
|
||||
? BLANK_1_OPTIONS
|
||||
: BLANK_4_OPTIONS;
|
||||
|
||||
return (
|
||||
<span
|
||||
key={`part-${lineIndex}-${partIndex}`}
|
||||
>
|
||||
{part}
|
||||
<input
|
||||
name={`blank-${currentIndex}`}
|
||||
className={`inline-block w-[100px] h-[36px] mx-2 bg-transparent border-b-2 border-dashed [font-size:26px_!important] text-center outline-none ${
|
||||
blankErrors[currentIndex]
|
||||
? "border-red-500"
|
||||
: "border-[#4e402e]"
|
||||
}`}
|
||||
value={blankValues[currentIndex] ?? ""}
|
||||
onChange={(e) => {
|
||||
const next = [...blankValues];
|
||||
next[currentIndex] = e.target.value;
|
||||
setBlankValues(next);
|
||||
|
||||
// 用户再次输入时,清除该空的错误高亮
|
||||
if (blankErrors[currentIndex]) {
|
||||
const nextErrs = [...blankErrors];
|
||||
nextErrs[currentIndex] = false;
|
||||
setBlankErrors(nextErrs);
|
||||
{isLimitedFill ? (
|
||||
// 限填:下拉选择
|
||||
<Select
|
||||
className="inline"
|
||||
options={options.map((opt) => ({
|
||||
value: opt,
|
||||
label: opt,
|
||||
}))}
|
||||
value={
|
||||
blankValues[currentIndex] ?? ""
|
||||
}
|
||||
}}
|
||||
/>
|
||||
onChange={(selectedValue) => {
|
||||
const next = [...blankValues];
|
||||
next[currentIndex] = selectedValue;
|
||||
setBlankValues(next);
|
||||
|
||||
// 用户再次选择时,清除该空的错误高亮
|
||||
if (blankErrors[currentIndex]) {
|
||||
const nextErrs = [...blankErrors];
|
||||
nextErrs[currentIndex] = false;
|
||||
setBlankErrors(nextErrs);
|
||||
}
|
||||
}}
|
||||
placeholder="请选择"
|
||||
error={blankErrors[currentIndex]}
|
||||
zIndex={10000}
|
||||
fitContent={true}
|
||||
/>
|
||||
) : (
|
||||
// 任填:文本输入,限制20个字,敏感词过滤
|
||||
<input
|
||||
name={`blank-${currentIndex}`}
|
||||
className={`inline-block w-[100px] h-[36px] mx-2 bg-transparent border-b-2 border-dashed [font-size:26px_!important] text-center outline-none ${
|
||||
blankErrors[currentIndex]
|
||||
? "border-red-500"
|
||||
: "border-[#4e402e]"
|
||||
}`}
|
||||
autoComplete="off"
|
||||
value={
|
||||
blankValues[currentIndex] ?? ""
|
||||
}
|
||||
maxLength={20}
|
||||
onChange={(e) => {
|
||||
const rawValue = e.target.value;
|
||||
// 应用敏感词过滤
|
||||
const filteredValue =
|
||||
filterInput(rawValue);
|
||||
|
||||
const next = [...blankValues];
|
||||
next[currentIndex] = filteredValue;
|
||||
setBlankValues(next);
|
||||
|
||||
// 用户再次输入时,清除该空的错误高亮
|
||||
if (blankErrors[currentIndex]) {
|
||||
const nextErrs = [...blankErrors];
|
||||
nextErrs[currentIndex] = false;
|
||||
setBlankErrors(nextErrs);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
})}
|
||||
@@ -334,7 +457,10 @@ const LetterToHuangyanpei = ({
|
||||
src={getStaticImage("components/letter/寄出")}
|
||||
alt="寄出"
|
||||
onClick={() => {
|
||||
onSubmit?.(blankValues);
|
||||
// 寄出前再次校验,确保所有填空符合标准
|
||||
if (validateBlanks()) {
|
||||
onSubmit?.(blankValues);
|
||||
}
|
||||
}}
|
||||
button
|
||||
/>
|
||||
|
||||
@@ -82,7 +82,7 @@ const LetterToSunzhongshan = ({
|
||||
textOrientation: "upright",
|
||||
}}
|
||||
>
|
||||
写给孙中山爷爷的信
|
||||
写给孙中山先生的信
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user