This commit is contained in:
2025-12-02 17:01:07 +08:00
parent efd244f930
commit 4299d4a8df
9 changed files with 202 additions and 152 deletions

View File

@@ -32,7 +32,8 @@ const EditModal: React.FC<EditModalProps> = ({
const [value, setValue] = useState(initialValue);
const [isValid, setIsValid] = useState(true);
const [isIllegal, setIsIllegal] = useState(false);
const [hasIllegal, setHasIllegal] = useState(true);
const [canEdit, setCanEdit] = useState(true);
// 使用全局键盘状态
const {
keyboardHeight,
@@ -64,19 +65,18 @@ const EditModal: React.FC<EditModalProps> = ({
}, [visible, initialValue]);
const createExcludeRegex = (chars: string) => {
// 转义正则表达式特殊字符
const escapedChars = chars.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
// 构建负向字符类正则表达式
// ^[^...]*$ 匹配不包含任何指定字符的完整字符串
const pattern = `[${escapedChars}]`;
return new RegExp(pattern);
};
const handle_input_change = (e: any) => {
const new_value = e.detail.value;
setValue(new_value);
let ishasIllegal = false;
if (type === "nickname") {
ishasIllegal = createExcludeRegex(invalidCharacters).test(new_value);
setHasIllegal(ishasIllegal);
}
const illegal =
/\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|ALTER|CREATE|EXEC|DECLARE)\b|('|--|\/\*|\*\/|;|#)|(=|'|"|`|\\|\|\|&&)|\bOR\s+['"]?[\w]+['"]?\s*=\s*['"]?[\w]+['"]?|\bUNION\s+SELECT\b|\bDROP\s+TABLE\b|\bINSERT\s+INTO\b|\bUPDATE\s+[\w]+\s+SET\b|\bDELETE\s+FROM\b/i.test(
new_value
@@ -86,22 +86,32 @@ const EditModal: React.FC<EditModalProps> = ({
const valid =
new_value.length >= 2 &&
new_value.length <= maxLength &&
!createExcludeRegex(invalidCharacters).test(new_value);
!ishasIllegal &&
!illegal &&
canEdit;
setIsValid(valid);
};
const handle_save = () => {
if (!isValid) {
if (isIllegal) {
Taro.showToast({
title: validationMessage || `请填写 2-${maxLength} 个字符`,
title: "输入的字符非法",
icon: "none",
duration: 2000,
});
return;
}
if (isIllegal) {
if (hasIllegal) {
Taro.showToast({
title: "输入的字符非法",
title: "内容不能包含@<>/等无效字符",
icon: "none",
duration: 2000,
});
return;
}
if (!isValid) {
Taro.showToast({
title: validationMessage || `请填写 2-${maxLength} 个字符`,
icon: "none",
duration: 2000,
});
@@ -171,7 +181,14 @@ const EditModal: React.FC<EditModalProps> = ({
</View>
</>
) : (
<View style={{display: "flex", flexDirection: "column", width: "100%", height: "120px"}}>
<View
style={{
display: "flex",
flexDirection: "column",
width: "100%",
height: "120px",
}}
>
<Textarea
className="text_input profile"
value={value}
@@ -196,7 +213,12 @@ const EditModal: React.FC<EditModalProps> = ({
</View>
{/* 验证提示 */}
{isIllegal ? (
<View className="validation_message">
<Text className="validation_text">
{validationMessage || `请填写 2-${maxLength} 个字符`}
</Text>
</View>
{/* {isIllegal ? (
<View className="validation_message">
<Text className="validation_text illegal">输入的字符非法</Text>
</View>
@@ -208,13 +230,13 @@ const EditModal: React.FC<EditModalProps> = ({
</Text>
</View>
)
)}
)} */}
</View>
{/* 底部按钮 */}
<View className="modal_footer">
<View
className={`save_button ${!isValid || isIllegal ? "disabled" : ""}`}
className={`save_button ${!isValid ? "disabled" : ""}`}
onClick={handle_save}
>
<Text className="save_text"></Text>

View File

@@ -19,7 +19,7 @@ import Picker from "../Picker/Picker";
import CloseIcon from "@/static/ntrp/ntrp_popup_close.svg";
import styles from "./index.module.scss";
const ntrpLevels = ["1.5", "2.0", "2.5", "3.0", "3.5", "4.0", "4.5"];
const ntrpLevels = ["1.5", "2.0", "2.5", "3.0", "3.5", "4.0", "4.5", "4.5+"];
const options = [
ntrpLevels.map((item) => ({
text: item,

View File

@@ -1,80 +1,88 @@
import React, { useState, useCallback, useEffect } from 'react'
import { Picker, ConfigProvider } from '@nutui/nutui-react-taro'
import { View } from '@tarojs/components'
import styles from './index.module.scss'
import React, { useState, useCallback, useEffect } from "react";
import { Picker, ConfigProvider } from "@nutui/nutui-react-taro";
import { View } from "@tarojs/components";
import styles from "./index.module.scss";
interface PickerOption {
text: string | number
value: string | number
text: string | number;
value: string | number;
}
interface PickerProps {
visible: boolean
options?: PickerOption[][]
defaultValue?: (string | number)[]
onConfirm?: (options: PickerOption[], values: (string | number)[]) => void
onChange?: (options: PickerOption[], values: (string | number)[], columnIndex: number) => void
visible: boolean;
options?: PickerOption[][];
defaultValue?: (string | number)[];
onConfirm?: (options: PickerOption[], values: (string | number)[]) => void;
onChange?: (
options: PickerOption[],
values: (string | number)[],
columnIndex: number
) => void;
}
const CustomPicker = ({
visible,
options = [],
defaultValue = [],
onConfirm,
onChange
const CustomPicker = ({
visible,
options = [],
defaultValue = [],
onConfirm,
onChange,
}: PickerProps) => {
// 使用内部状态管理当前选中的值
const [currentValue, setCurrentValue] = useState<(string | number)[]>(defaultValue)
const [currentValue, setCurrentValue] =
useState<(string | number)[]>(defaultValue);
// 当外部 defaultValue 变化时,同步更新内部状态
useEffect(() => {
handleValuesChange(defaultValue);
}, [defaultValue])
}, [defaultValue]);
const confirmPicker = (
options: PickerOption[],
values: (string | number)[]
) => {
let description = ''
let description = "";
options.forEach((option: any) => {
description += ` ${option.text}`
})
description += ` ${option.text}`;
});
if (onConfirm) {
onConfirm(options, values)
onConfirm(options, values);
}
}
};
const handleValuesChange = (valuesList) => {
const isSame =
Array.isArray(valuesList) &&
Array.isArray(currentValue) &&
valuesList.length === currentValue.length &&
valuesList.every((v: any, idx: number) => v === currentValue[idx])
valuesList.every((v: any, idx: number) => v === currentValue[idx]);
if (!isSame) {
setCurrentValue(valuesList)
setCurrentValue(valuesList);
}
return isSame;
}
const changePicker = useCallback((options: any[], values: any, columnIndex: number) => {
// 值相同则不触发更新,避免受控/非受控同步造成的回流循环
const isSame = handleValuesChange(values)
if (onChange && !isSame) {
onChange(options, values, columnIndex)
}
}, [onChange, currentValue])
};
const changePicker = useCallback(
(options: any[], values: any, columnIndex: number) => {
// 值相同则不触发更新,避免受控/非受控同步造成的回流循环
const isSame = handleValuesChange(values);
if (onChange && !isSame) {
onChange(options, values, columnIndex);
}
},
[onChange, currentValue]
);
return (
<>
<View className={styles['picker-container']}>
<View className={styles["picker-container"]}>
<ConfigProvider
theme={{
nutuiPickerItemHeight: '48px',
nutuiPickerItemActiveLineBorder: 'none',
nutuiPickerItemTextColor: '#000',
nutuiPickerItemFontSize: '20px',
nutuiPickerItemHeight: "48px",
nutuiPickerItemActiveLineBorder: "none",
nutuiPickerItemTextColor: "#000",
nutuiPickerItemFontSize: "20px",
}}
>
<Picker
@@ -92,7 +100,7 @@ const CustomPicker = ({
</ConfigProvider>
</View>
</>
)
}
);
};
export default CustomPicker
export default CustomPicker;

View File

@@ -152,25 +152,6 @@ const PopupPicker = ({
<View className={styles.evaluateCardWrap}>
<NTRPTestEntryCard type={EvaluateScene.userEdit} />
</View>
// <View className={`${styles["examination-btn"]}}`}>
// <View className={`${styles["text-container"]}}`}>
// <View className={`${styles["text-title"]}}`}>
// 不知道自己的<Text>NTRP</Text>水平
// </View>
// <View className={`${styles["text-btn"]}}`}>
// <Text>快速测试</Text>
// <Image src={imgs.ICON_ARROW_GREEN} className={`${styles["icon-arrow"]}`}></Image>
// </View>
// </View>
// <View className={`${styles["img-container"]}}`}>
// <View className={`${styles["img-box"]}`}>
// <Image src={img!}></Image>
// </View>
// <View className={`${styles["img-box"]}`}>
// <Image src={imgs.ICON_EXAMINATION}></Image>
// </View>
// </View>
// </View>
)}
<Picker
visible={visible}

View File

@@ -607,7 +607,7 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
onCancel={handle_edit_modal_cancel}
validationMessage={
editing_field === "nickname"
? "请填写 2-24 个字符,不包括 @<>/等无效字符"
? "请填写 2-24 个字符,不包括 @<>/等无效字符。30 天内可修改 4 次昵称12.5 前还可修改 4 次。"
: "请填写 2-100 个字符"
}
/>
@@ -663,26 +663,20 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
title="选择 NTRP 自评水平"
ntrpTested={ntrpTested}
options={[
[
{ text: "1.5", value: "1.5" },
{ text: "2.0", value: "2.0" },
{ text: "2.5", value: "2.5" },
{ text: "3.0", value: "3.0" },
{ text: "3.5", value: "3.5" },
{ text: "4.0", value: "4.0" },
{ text: "4.5", value: "4.5" },
{ text: "4.5+", value: "4.5+" },
],
{ text: "1.5", value: "1.5" },
{ text: "2.0", value: "2.0" },
{ text: "2.5", value: "2.5" },
{ text: "3.0", value: "3.0" },
{ text: "3.5", value: "3.5" },
{ text: "4.0", value: "4.0" },
{ text: "4.5", value: "4.5" },
{ text: "4.5+", value: "4.5+" },
]}
type="ntrp"
img={user_info.avatar_url || ""}
visible={ntrp_picker_visible}
setvisible={setNtrpPickerVisible}
value={
!form_data.ntrp_level || form_data.ntrp_level === "0"
? ["2.5"]
: [form_data.ntrp_level]
}
value={[form_data.ntrp_level || "2.5"]}
onChange={handle_ntrp_level_change}
/>
)}