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,19 +1,23 @@
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 = ({
@@ -21,60 +25,64 @@ const CustomPicker = ({
options = [],
defaultValue = [],
onConfirm,
onChange
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 changePicker = useCallback(
(options: any[], values: any, columnIndex: number) => {
// 值相同则不触发更新,避免受控/非受控同步造成的回流循环
const isSame = handleValuesChange(values)
const isSame = handleValuesChange(values);
if (onChange && !isSame) {
onChange(options, values, columnIndex)
onChange(options, values, columnIndex);
}
}, [onChange, currentValue])
},
[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,7 +663,6 @@ 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" },
@@ -672,17 +671,12 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
{ 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}
/>
)}

View File

@@ -251,6 +251,9 @@
text-overflow: ellipsis;
white-space: nowrap;
text-align: right;
&.placeholder {
color: rgba(60, 60, 67, 0.3);
}
}
.bio_textarea {

View File

@@ -27,10 +27,10 @@ const EditProfilePage: React.FC = () => {
nickname: info?.nickname ?? "",
personal_profile: info?.personal_profile ?? "",
occupation: info?.occupation ?? "",
ntrp_level: info?.ntrp_level ?? "2.5",
ntrp_level: info?.ntrp_level ?? "",
phone: info?.phone ?? "",
gender: info?.gender ?? "",
birthday: info?.birthday ?? "2000-01-01",
birthday: info?.birthday ?? "",
country: info?.country ?? "",
province: info?.province ?? "",
city: info?.city ?? "",
@@ -68,10 +68,10 @@ const EditProfilePage: React.FC = () => {
nickname: info?.nickname ?? "",
personal_profile: info?.personal_profile ?? "",
occupation: info?.occupation ?? "",
ntrp_level: info?.ntrp_level ?? "2.5",
ntrp_level: info?.ntrp_level ?? "",
phone: info?.phone ?? "",
gender: info?.gender ?? "",
birthday: info?.birthday ?? "2000-01-01",
birthday: info?.birthday ?? "",
country: info?.country ?? "",
province: info?.province ?? "",
city: info?.city ?? "",
@@ -350,15 +350,15 @@ const EditProfilePage: React.FC = () => {
// 处理NTRP水平选择
const handle_ntrp_level_change = (e: any) => {
if (!Array.isArray(e) || e.length === 0 || e[0] === undefined) {
Taro.showToast({
title: "请选择NTRP水平",
icon: "none",
});
return;
}
// if (!Array.isArray(e) || e.length === 0 || e[0] === undefined) {
// Taro.showToast({
// title: "请选择NTRP水平",
// icon: "none",
// });
// return;
// }
const ntrp_level_value = e[0];
handle_field_edit("ntrp_level", String(ntrp_level_value));
handle_field_edit("ntrp_level", ntrp_level_value);
};
// 处理职业选择
@@ -543,7 +543,11 @@ const EditProfilePage: React.FC = () => {
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">
<Text
className={`item_value ${
form_data.gender ? "" : "placeholder"
}`}
>
{convert_db_gender_to_display(form_data.gender)}
</Text>
<Image
@@ -572,7 +576,13 @@ const EditProfilePage: React.FC = () => {
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.birthday}</Text>
<Text
className={`item_value ${
form_data.birthday ? "" : "placeholder"
}`}
>
{form_data.birthday || "选择生日"}
</Text>
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
@@ -597,7 +607,11 @@ const EditProfilePage: React.FC = () => {
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">
<Text
className={`item_value ${
form_data.personal_profile ? "" : "placeholder"
}`}
>
{form_data.personal_profile.replace(/\n/g, " ") ||
"介绍一下自己"}
</Text>
@@ -626,7 +640,19 @@ const EditProfilePage: React.FC = () => {
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{`${form_data.country} ${form_data.province} ${form_data.city}`}</Text>
<Text
className={`item_value ${
form_data.country ||
form_data.province ||
form_data.city
? ""
: "placehoder"
}`}
>
{form_data.country || form_data.province || form_data.city
? `${form_data.country} ${form_data.province} ${form_data.city}`
: "选择所在地区"}
</Text>
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
@@ -648,7 +674,13 @@ const EditProfilePage: React.FC = () => {
<Text className="item_label">NTRP </Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.ntrp_level}</Text>
<Text
className={`item_value ${
form_data.ntrp_level ? "" : "placeholder"
}`}
>
{form_data.ntrp_level || "测测你的 NTRP 水平"}
</Text>
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
@@ -669,8 +701,18 @@ const EditProfilePage: React.FC = () => {
/>
<Text className="item_label"></Text>
</View>
<View className="item_right">
<Text className="item_value">{form_data.occupation}</Text>
<View
className={`item_right ${
form_data.occupation ? "" : "placeholder"
}`}
>
<Text
className={`item_value ${
form_data.occupation ? "" : "placeholder"
}`}
>
{form_data.occupation || "填写你的职业"}
</Text>
<Image
className="arrow_icon"
src={require("@/static/list/icon-list-right-arrow.svg")}
@@ -769,7 +811,7 @@ const EditProfilePage: React.FC = () => {
onCancel={handle_edit_modal_cancel}
validationMessage={
editing_field === "nickname"
? "请填写 2-24 个字符,不包括 @<>/等无效字符"
? "请填写 2-24 个字符,不包括 @<>/等无效字符。30 天内可修改 4 次昵称12.5 前还可修改 4 次。"
: "请填写 2-100 个字符"
}
/>
@@ -803,9 +845,9 @@ const EditProfilePage: React.FC = () => {
visible={birthday_picker_visible}
setvisible={setBirthdayPickerVisible}
value={[
new Date(form_data.birthday).getFullYear(),
new Date(form_data.birthday).getMonth() + 1,
new Date(form_data.birthday).getDate(),
new Date(form_data.birthday || Date.now()).getFullYear(),
new Date(form_data.birthday || Date.now()).getMonth() + 1,
new Date(form_data.birthday || Date.now()).getDate(),
]}
type="day"
onChange={handle_birthday_change}
@@ -836,7 +878,6 @@ const EditProfilePage: React.FC = () => {
confirmText="保存"
ntrpTested={ntrpTested}
options={[
[
{ text: "1.5", value: "1.5" },
{ text: "2.0", value: "2.0" },
{ text: "2.5", value: "2.5" },
@@ -845,13 +886,12 @@ const EditProfilePage: React.FC = () => {
{ text: "4.0", value: "4.0" },
{ text: "4.5", value: "4.5" },
{ text: "4.5+", value: "4.5+" },
],
]}
type="ntrp"
// img={(user_info as UserInfoType)?.avatar_url}
visible={ntrp_picker_visible}
setvisible={setNtrpPickerVisible}
value={form_data.ntrp_level === "" ? ["2.5"] : [form_data.ntrp_level]}
value={[form_data.ntrp_level || "2.5"]}
onChange={handle_ntrp_level_change}
/>
)}

View File

@@ -182,7 +182,7 @@
padding: 12px 16px 12px 12px;
height: 40px;
background: #000000;
border: 0.5px solid rgba(0, 0, 0, 0.06);
border: 0.5pt solid rgba(0, 0, 0, 0.06);
border-radius: 999px;
cursor: pointer;
transition: all 0.3s ease;

View File

@@ -12,14 +12,14 @@
*/
export const convert_db_gender_to_display = (db_gender: string): string => {
switch (db_gender) {
case '0':
return '男';
case '1':
return '女';
case '2':
return '保密';
case "0":
return "男";
case "1":
return "女";
case "2":
return "保密";
default:
return '未知';
return "选择性别";
}
};
@@ -28,16 +28,18 @@ export const convert_db_gender_to_display = (db_gender: string): string => {
* @param display_gender 页面显示文本 ('男' | '女')
* @returns 数据库性别值 ('0' | '1')
*/
export const convert_display_gender_to_db = (display_gender: string): string => {
export const convert_display_gender_to_db = (
display_gender: string
): string => {
switch (display_gender) {
case '男':
return '0';
case '女':
return '1';
case '保密':
return '2';
case "男":
return "0";
case "女":
return "1";
case "保密":
return "2";
default:
return '0'; // 默认返回男性
return "0"; // 默认返回男性
}
};
@@ -49,11 +51,11 @@ export const convert_display_gender_to_db = (display_gender: string): string =>
export const convert_wechat_gender_to_db = (wechat_gender: number): string => {
switch (wechat_gender) {
case 1: // 微信1 = 男
return '0'; // 数据库:'0' = 男
return "0"; // 数据库:'0' = 男
case 2: // 微信2 = 女
return '1'; // 数据库:'1' = 女
return "1"; // 数据库:'1' = 女
default: // 微信0 = 未知
return '0'; // 默认返回男性
return "0"; // 默认返回男性
}
};
@@ -64,9 +66,9 @@ export const convert_wechat_gender_to_db = (wechat_gender: number): string => {
*/
export const convert_db_gender_to_wechat = (db_gender: string): number => {
switch (db_gender) {
case '0':
case "0":
return 1; // 微信1 = 男
case '1':
case "1":
return 2; // 微信2 = 女
default:
return 1; // 默认返回男性