下拉隐藏简介等信息,优化超出文本长度交互

This commit is contained in:
2025-11-24 22:42:48 +08:00
parent 78ab8c9a42
commit 31194f67be
7 changed files with 222 additions and 156 deletions

View File

@@ -140,6 +140,9 @@
font-size: 14px;
line-height: 1.71em;
color: rgba(60, 60, 67, 0.3);
&.un-valid {
color: #FF3B30;
}
}
}
}
@@ -153,6 +156,9 @@
font-size: 12px;
line-height: 1.5em;
color: rgba(60, 60, 67, 0.6);
&.illegal {
color: #FF3B30;
}
}
}
}

View File

@@ -29,6 +29,7 @@ const EditModal: React.FC<EditModalProps> = ({
}) => {
const [value, setValue] = useState(initialValue);
const [isValid, setIsValid] = useState(true);
const [isIllegal, setIsIllegal] = useState(false);
// 使用全局键盘状态
const { keyboardHeight, isKeyboardVisible, addListener, initializeKeyboardListener } = useKeyboardHeight()
@@ -58,6 +59,8 @@ const EditModal: React.FC<EditModalProps> = ({
const new_value = e.detail.value;
setValue(new_value);
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)
setIsIllegal(illegal)
// 验证输入
const valid = new_value.length >= 2 && new_value.length <= maxLength;
setIsValid(valid);
@@ -72,6 +75,14 @@ const EditModal: React.FC<EditModalProps> = ({
});
return;
}
if (isIllegal) {
Taro.showToast({
title: "输入的字符非法",
icon: 'none',
duration: 2000
});
return;
}
onSave(value);
};
@@ -104,48 +115,61 @@ const EditModal: React.FC<EditModalProps> = ({
<View className="input_container">
{type === 'nickname' ? (
<Input
className="text_input nickname_input"
value={value}
type="nickname"
placeholder={placeholder}
maxlength={maxLength}
onInput={handle_input_change}
adjustPosition={false}
confirmType="done"
autoFocus={true}
/>
<>
<Input
className="text_input nickname_input"
value={value}
type="nickname"
placeholder={placeholder}
// maxlength={maxLength}
onInput={handle_input_change}
adjustPosition={false}
confirmType="done"
autoFocus={true}
/>
<View className="char_count">
<Text className={`count_text ${value.length > maxLength && "un-valid"}`}>{value.length}/{maxLength}</Text>
</View>
</>
) : (
<>
<Textarea
className="text_input"
value={value}
placeholder={placeholder}
maxlength={maxLength}
// maxlength={maxLength}
onInput={handle_input_change}
autoFocus={true}
adjustPosition={false}
/>
<View className="char_count">
<Text className="count_text">{value.length}/{maxLength}</Text>
<Text className={`count_text ${value.length > maxLength && "un-valid"}`}>{value.length}/{maxLength}</Text>
</View>
</>
)}
</View>
{/* 验证提示 */}
{!isValid && (
<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> :
!isValid && (
<View className="validation_message">
<Text className="validation_text">
{validationMessage || `请填写 2-${maxLength} 个字符`}
</Text>
</View>
)
}
</View>
{/* 底部按钮 */}
<View className="modal_footer">
<View className={`save_button ${!isValid ? "disabled" : ""}`} onClick={handle_save}>
<View className={`save_button ${!isValid || isIllegal ? "disabled" : ""}`} onClick={handle_save}>
<Text className="save_text"></Text>
</View>
</View>

View File

@@ -320,6 +320,7 @@
line-height: 1.571em;
color: rgba(0, 0, 0, 0.65);
white-space: pre-line;
word-break: break-all;
}
}
}
@@ -558,4 +559,4 @@
}
}
}
}
}

View File

@@ -47,6 +47,8 @@ interface UserInfoCardProps {
user_info: Partial<UserInfoType>;
is_current_user: boolean;
is_following?: boolean;
collapseProfile?: boolean;
setMarginBottom?: boolean;
on_follow?: () => void;
on_message?: () => void;
on_share?: () => void;
@@ -66,12 +68,15 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
user_info,
is_current_user,
is_following = false,
collapseProfile,
setMarginBottom = true,
on_follow,
on_message,
on_share,
set_user_info,
onTab,
}) => {
const { setShowGuideBar } = useGlobalState();
const { updateUserInfo } = useUserActions();
@@ -170,11 +175,13 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
return;
}
if (field === "nickname") {
if (!is_current_user) return;
// 手动输入
setShowGuideBar(false);
setEditingField(field);
setEditModalVisible(true);
} else {
if (!is_current_user) return;
setShowGuideBar(false);
setEditingField(field);
setEditModalVisible(true);
@@ -376,7 +383,7 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
{/* 统计数据 */}
<View className="stats_section">
<View className="stats_container">
<View className="stats_container" style={{ marginBottom: `${collapseProfile && setMarginBottom ? "16px" : "unset"}` }}>
<View
className="stat_item clickable"
onClick={() => handle_stats_click("following")}
@@ -454,114 +461,121 @@ const UserInfoCardComponent: React.FC<UserInfoCardProps> = ({
</View>
{/* 标签和简介 */}
<View className="tags_bio_section">
<View className="tags_container">
{user_info.gender && user_info.gender !== "2" ? (
<View className="tag_item">
{user_info.gender === "0" && (
<Image
className="tag_icon"
src={require("../../static/userInfo/male.svg")}
{
!collapseProfile ?
<View className="tags_bio_section">
<View className="tags_container">
{user_info.gender && user_info.gender !== "2" ? (
<View className="tag_item">
{user_info.gender === "0" && (
<Image
className="tag_icon"
src={require("../../static/userInfo/male.svg")}
onClick={() => {
editable && handle_open_edit_modal("gender");
}}
/>
)}
{user_info.gender === "1" && (
<Image
className="tag_icon"
src={require("../../static/userInfo/female.svg")}
onClick={() => {
editable && handle_open_edit_modal("gender");
}}
/>
)}
</View>
) : is_current_user && user_info.gender !== "2" ? (
<View
className="button_edit"
onClick={() => {
editable && handle_open_edit_modal("gender");
handle_open_edit_modal("gender");
}}
/>
)}
{user_info.gender === "1" && (
<Image
className="tag_icon"
src={require("../../static/userInfo/female.svg")}
>
<Text></Text>
</View>
) : null}
{user_info.ntrp_level !== "" ? (
<View
className="tag_item"
onClick={() => {
editable && handle_open_edit_modal("gender");
editable && handle_open_edit_modal("ntrp_level");
}}
/>
)}
>
<Text className="tag_text">{`NTRP ${formatNtrpDisplay(
user_info.ntrp_level
)}`}</Text>
</View>
) : is_current_user ? (
<View
className="button_edit"
onClick={() => {
handle_open_edit_modal("ntrp_level");
}}
>
<Text>NTRP水平</Text>
</View>
) : null}
{user_info.occupation ? (
<View
className="tag_item"
onClick={() => {
editable && handle_open_edit_modal("occupation");
}}
>
<Text className="tag_text">
{user_info.occupation.split(" ")[2]}
</Text>
</View>
) : is_current_user ? (
<View
className="button_edit"
onClick={() => {
handle_open_edit_modal("occupation");
}}
>
<Text></Text>
</View>
) : null}
{user_info.country || user_info.province || user_info.city ? (
<View
className="tag_item"
onClick={() => editable && handle_open_edit_modal("location")}
>
<Text className="tag_text">{`${user_info.province}${user_info.city}`}</Text>
</View>
) : is_current_user ? (
<View
className="button_edit"
onClick={() => handle_open_edit_modal("location")}
>
<Text></Text>
</View>
) : null}
</View>
) : is_current_user && user_info.gender !== "2" ? (
<View
className="button_edit"
onClick={() => {
handle_open_edit_modal("gender");
}}
className="personal_profile"
onClick={() => handle_open_edit_modal("personal_profile")}
>
<Text></Text>
{!collapseProfile ?
user_info.personal_profile ? (
<Text className="bio_text">{user_info.personal_profile}</Text>
) : is_current_user ? (
<View className="personal_profile_edit">
<Image
className="edit_icon"
src={require("../../static/userInfo/info_edit.svg")}
/>
<Text className="bio_text"></Text>
</View>
) :
null :
null}
</View>
) : null}
{user_info.ntrp_level !== "" ? (
<View
className="tag_item"
onClick={() => {
editable && handle_open_edit_modal("ntrp_level");
}}
>
<Text className="tag_text">{`NTRP ${formatNtrpDisplay(
user_info.ntrp_level
)}`}</Text>
</View>
) : is_current_user ? (
<View
className="button_edit"
onClick={() => {
handle_open_edit_modal("ntrp_level");
}}
>
<Text>NTRP水平</Text>
</View>
) : null}
{user_info.occupation ? (
<View
className="tag_item"
onClick={() => {
editable && handle_open_edit_modal("occupation");
}}
>
<Text className="tag_text">
{user_info.occupation.split(" ")[2]}
</Text>
</View>
) : is_current_user ? (
<View
className="button_edit"
onClick={() => {
handle_open_edit_modal("occupation");
}}
>
<Text></Text>
</View>
) : null}
{user_info.country || user_info.province || user_info.city ? (
<View
className="tag_item"
onClick={() => editable && handle_open_edit_modal("location")}
>
<Text className="tag_text">{`${user_info.province}${user_info.city}`}</Text>
</View>
) : is_current_user ? (
<View
className="button_edit"
onClick={() => handle_open_edit_modal("location")}
>
<Text></Text>
</View>
) : null}
</View>
<View
className="personal_profile"
onClick={() => handle_open_edit_modal("personal_profile")}
>
{user_info.personal_profile ? (
<Text className="bio_text">{user_info.personal_profile}</Text>
) : is_current_user ? (
<View className="personal_profile_edit">
<Image
className="edit_icon"
src={require("../../static/userInfo/info_edit.svg")}
/>
<Text className="bio_text"></Text>
</View>
) : null}
</View>
</View>
</View> :
null
}
{/* 编辑个人简介弹窗 */}
<EditModal
@@ -687,7 +701,8 @@ const arePropsEqual = (
prevUserInfoStr === nextUserInfoStr &&
prevProps.editable === nextProps.editable &&
prevProps.is_current_user === nextProps.is_current_user &&
prevProps.is_following === nextProps.is_following
prevProps.is_following === nextProps.is_following &&
prevProps.collapseProfile === nextProps.collapseProfile
);
};
@@ -827,9 +842,8 @@ export const GameTabs: React.FC<GameTabsProps> = ({
<Text className="tab_text">{hosted_text}</Text>
</View>
<View
className={`tab_item ${
active_tab === "participated" ? "active" : ""
}`}
className={`tab_item ${active_tab === "participated" ? "active" : ""
}`}
onClick={() => on_tab_change("participated")}
>
<Text className="tab_text">{participated_text}</Text>

View File

@@ -30,6 +30,8 @@ const MyselfPageContent: React.FC = () => {
"hosted"
);
const [collapseProfile, setCollapseProfile] = useState(false);
useEffect(() => {
pickerOption.getCities();
pickerOption.getProfessions();
@@ -139,8 +141,13 @@ const MyselfPageContent: React.FC = () => {
setActiveTab(tab);
};
const handleScroll = (event: any) => {
const scrollData = event.detail;
setCollapseProfile(scrollData.scrollTop > 10);
}
return (
<View className={styles.myselfPage}>
<ScrollView scrollY className={styles.myselfPage} onScroll={handleScroll}>
<View
className={styles.myselfPageContentMain}
style={{ paddingTop: `${totalHeight}px` }}
@@ -151,28 +158,33 @@ const MyselfPageContent: React.FC = () => {
user_info={user_info}
is_current_user={is_current_user}
is_following={is_following}
collapseProfile={collapseProfile}
on_follow={handle_follow}
onTab={handleOnTab}
/>
<View className={styles.quickActionsSection}>
<View className={styles.actionCard}>
<View className={styles.actionContent} onClick={handle_game_orders}>
<Image
className={styles.actionIcon}
src={require("@/static/userInfo/order_btn.svg")}
/>
<Text className={styles.actionText}></Text>
</View>
<View className={styles.actionDivider}></View>
<View className={styles.actionContent} onClick={handle_wallet}>
<Image
className={styles.actionIcon}
src={require("@/static/userInfo/wallet.svg")}
/>
<Text className={styles.actionText}></Text>
</View>
</View>
</View>
{
!collapseProfile ?
<View className={styles.quickActionsSection}>
<View className={styles.actionCard}>
<View className={styles.actionContent} onClick={handle_game_orders}>
<Image
className={styles.actionIcon}
src={require("@/static/userInfo/order_btn.svg")}
/>
<Text className={styles.actionText}></Text>
</View>
<View className={styles.actionDivider}></View>
<View className={styles.actionContent} onClick={handle_wallet}>
<Image
className={styles.actionIcon}
src={require("@/static/userInfo/wallet.svg")}
/>
<Text className={styles.actionText}></Text>
</View>
</View>
</View> :
null
}
</View>
<View className={styles.testEntryCardBox}>
@@ -188,9 +200,8 @@ const MyselfPageContent: React.FC = () => {
<Text className={styles.tabText}></Text>
</View>
<View
className={`${styles.tabItem} ${
active_tab === "participated" ? styles.active : ""
}`}
className={`${styles.tabItem} ${active_tab === "participated" ? styles.active : ""
}`}
onClick={() => setActiveTab("participated")}
>
<Text className={styles.tabText}></Text>
@@ -211,7 +222,7 @@ const MyselfPageContent: React.FC = () => {
btnImg="ICON_ADD"
reload={goPublish}
isShowNoData={game_records.length === 0}
loadMoreMatches={() => {}}
loadMoreMatches={() => { }}
collapse={true}
style={{ paddingBottom: 0, overflow: "hidden" }}
listLoadErrorHeight="320px"
@@ -230,7 +241,7 @@ const MyselfPageContent: React.FC = () => {
error={null}
errorImg="ICON_LIST_EMPTY"
isShowNoData={ended_game_records.length === 0}
loadMoreMatches={() => {}}
loadMoreMatches={() => { }}
collapse={true}
style={{ paddingBottom: "90px", overflow: "hidden" }}
listLoadErrorHeight="320px"
@@ -239,7 +250,7 @@ const MyselfPageContent: React.FC = () => {
</ScrollView>
</View>
</View>
</View>
</ScrollView>
);
};

View File

@@ -80,7 +80,7 @@
display: flex;
flex-direction: column;
gap: 16px;
margin-bottom: 16px;
// margin-bottom: 16px;
padding: 0 15px;
// margin-top: 98px;
@@ -273,6 +273,7 @@
line-height: 1.571em;
color: rgba(0, 0, 0, 0.65);
white-space: pre-line;
word-break: break-all;
}
}
}

View File

@@ -75,6 +75,8 @@ const OtherUserPage: React.FC = () => {
"hosted"
);
const [collapseProfile, setCollapseProfile] = useState(false);
// 页面加载时获取用户信息
useEffect(() => {
const load_user_data = async () => {
@@ -211,6 +213,11 @@ const OtherUserPage: React.FC = () => {
setActiveTab(tab)
}
const handleScroll = (event: any) => {
const scrollData = event.detail;
setCollapseProfile(scrollData.scrollTop > 10);
}
// 处理球局详情
// const handle_game_detail = (game_id: string) => {
// Taro.navigateTo({
@@ -219,7 +226,7 @@ const OtherUserPage: React.FC = () => {
// };
return (
<View className="other_user_page">
<ScrollView scrollY className="other_user_page" onScroll={handleScroll}>
{/* <CustomNavbar>
<View className="navbar_content">
<View className="navbar_back" onClick={() => Taro.navigateBack()}>
@@ -251,6 +258,8 @@ const OtherUserPage: React.FC = () => {
user_info={user_info}
is_current_user={false}
is_following={is_following}
collapseProfile={collapseProfile}
setMarginBottom={false}
on_follow={handle_follow}
on_message={handle_send_message}
onTab={handleOnTab}
@@ -344,7 +353,7 @@ const OtherUserPage: React.FC = () => {
</View>
</View>
{/* <GuideBar currentPage="personal" /> */}
</View>
</ScrollView>
);
};