From 0aa2448c92dea5bd6fea15c7ae940b2fff577ef5 Mon Sep 17 00:00:00 2001 From: gww Date: Tue, 27 Jan 2026 12:16:59 +0800 Subject: [PATCH] =?UTF-8?q?emailVerified=E5=8C=BA=E5=88=86=E9=82=AE?= =?UTF-8?q?=E7=AE=B1=E5=92=8C=E6=89=8B=E6=9C=BA=E5=8F=B7=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- @share/components/ConfirmModal.tsx | 21 +- app/profile.tsx | 329 ++++++++++++++++++++++++++++- app/settings.tsx | 4 +- 3 files changed, 339 insertions(+), 15 deletions(-) diff --git a/@share/components/ConfirmModal.tsx b/@share/components/ConfirmModal.tsx index 13722bd..9c04221 100644 --- a/@share/components/ConfirmModal.tsx +++ b/@share/components/ConfirmModal.tsx @@ -20,6 +20,8 @@ interface ConfirmModalProps { onConfirm?: () => void /** 取消回调,如果不传则默认关闭Modal */ onCancel?: () => void + /** 确认按钮是否处于加载状态 */ + confirmLoading?: boolean } const ConfirmModal: React.FC = ({ @@ -30,8 +32,10 @@ const ConfirmModal: React.FC = ({ cancelText = '取消', onConfirm, onCancel, + confirmLoading = false, }) => { const handleCancel = () => { + if (confirmLoading) return if (onCancel) { onCancel() } else { @@ -40,6 +44,7 @@ const ConfirmModal: React.FC = ({ } const handleConfirm = () => { + if (confirmLoading) return onConfirm && onConfirm() } @@ -69,10 +74,20 @@ const ConfirmModal: React.FC = ({ - {confirmText} - {title==='确认支付?' && ( - + {confirmLoading ? ( + <> + + {confirmText} + + ) : ( + <> + {confirmText} + {title==='确认支付?' && ( + + )} + )} diff --git a/app/profile.tsx b/app/profile.tsx index 69730db..a6711b8 100644 --- a/app/profile.tsx +++ b/app/profile.tsx @@ -55,6 +55,267 @@ function EditNicknameModal({ initialName, onConfirm, onCancel }: EditNicknameMod ) } +type ChangePasswordModalProps = { + onConfirm: (currentPassword: string, newPassword: string) => void | Promise + onCancel: () => void +} + +// 验证密码强度 +const validatePassword = (password: string): string | null => { + if (!password) { + return '请输入新密码' + } + if (password.length < 6) { + return '密码长度至少6位' + } + // 检查是否是无效密码:全是相同字符 + if (/^(.)\1+$/.test(password)) { + return '密码不能全是相同字符' + } + // 检查是否是连续数字(如 123456, 654321) + const isSequentialNumbers = (str: string): boolean => { + for (let i = 0; i < str.length - 1; i++) { + const current = parseInt(str[i], 10) + const next = parseInt(str[i + 1], 10) + if (isNaN(current) || isNaN(next)) return false + if (Math.abs(current - next) !== 1) return false + } + return true + } + // 检查是否是连续字母(如 abcdef, fedcba) + const isSequentialLetters = (str: string): boolean => { + const lowerStr = str.toLowerCase() + for (let i = 0; i < lowerStr.length - 1; i++) { + const current = lowerStr.charCodeAt(i) + const next = lowerStr.charCodeAt(i + 1) + if (Math.abs(current - next) !== 1) return false + } + return true + } + if (isSequentialNumbers(password) || isSequentialLetters(password)) { + return '密码不能是连续字符' + } + // 检查是否是简单密码(如 111111, 000000, aaaaaa) + const commonWeakPasswords = ['111111', '222222', '333333', '444444', '555555', '666666', '777777', '888888', '999999', '000000', 'aaaaaa', 'bbbbbb', 'cccccc', 'dddddd', 'eeeeee', 'ffffff', '123456', '654321', 'abcdef', 'fedcba'] + if (commonWeakPasswords.includes(password.toLowerCase())) { + return '密码过于简单,请使用更复杂的密码' + } + return null +} + +function ChangePasswordModal({ onConfirm, onCancel }: ChangePasswordModalProps) { + const [currentPassword, setCurrentPassword] = useState('') + const [newPassword, setNewPassword] = useState('') + const [confirmPassword, setConfirmPassword] = useState('') + const [showCurrentPassword, setShowCurrentPassword] = useState(false) + const [showNewPassword, setShowNewPassword] = useState(false) + const [showConfirmPassword, setShowConfirmPassword] = useState(false) + const [loading, setLoading] = useState(false) + const [errors, setErrors] = useState<{ + currentPassword?: string + newPassword?: string + confirmPassword?: string + }>({}) + + const validateCurrentPassword = (value: string) => { + if (!value) { + setErrors((prev) => ({ ...prev, currentPassword: '请输入当前密码' })) + } else { + setErrors((prev) => { + const newErrors = { ...prev } + delete newErrors.currentPassword + return newErrors + }) + } + } + + const validateNewPassword = (value: string) => { + const error = validatePassword(value) + if (error) { + setErrors((prev) => ({ ...prev, newPassword: error })) + } else { + setErrors((prev) => { + const newErrors = { ...prev } + delete newErrors.newPassword + return newErrors + }) + // 如果确认密码已输入,重新验证确认密码 + if (confirmPassword) { + validateConfirmPassword(confirmPassword, value) + } + } + } + + const validateConfirmPassword = (value: string, newPwd = newPassword) => { + if (!value) { + setErrors((prev) => ({ ...prev, confirmPassword: '请再次输入新密码' })) + } else if (value !== newPwd) { + setErrors((prev) => ({ ...prev, confirmPassword: '两次密码输入不一致' })) + } else { + setErrors((prev) => { + const newErrors = { ...prev } + delete newErrors.confirmPassword + return newErrors + }) + } + } + + const handleConfirm = async () => { + if (loading) return + + // 验证所有字段并收集错误 + const newErrors: typeof errors = {} + + if (!currentPassword) { + newErrors.currentPassword = '请输入当前密码' + } + + const newPasswordError = validatePassword(newPassword) + if (newPasswordError) { + newErrors.newPassword = newPasswordError + } + + if (!confirmPassword) { + newErrors.confirmPassword = '请再次输入新密码' + } else if (confirmPassword !== newPassword) { + newErrors.confirmPassword = '两次密码输入不一致' + } + + // 设置错误状态 + setErrors(newErrors) + + // 如果有错误,不继续提交 + if (Object.keys(newErrors).length > 0) { + return + } + + setLoading(true) + try { + await onConfirm(currentPassword, newPassword) + } finally { + setLoading(false) + } + } + + return ( + + + 当前密码 + + { + setCurrentPassword(text) + if (errors.currentPassword) { + validateCurrentPassword(text) + } + }} + onBlur={() => validateCurrentPassword(currentPassword)} + secureTextEntry={!showCurrentPassword} + /> + setShowCurrentPassword(!showCurrentPassword)} + > + + + + {errors.currentPassword && ( + {errors.currentPassword} + )} + + + 新密码 + + { + setNewPassword(text) + if (errors.newPassword) { + validateNewPassword(text) + } + }} + onBlur={() => validateNewPassword(newPassword)} + secureTextEntry={!showNewPassword} + /> + setShowNewPassword(!showNewPassword)} + > + + + + {errors.newPassword && ( + {errors.newPassword} + )} + + + 确认新密码 + + { + setConfirmPassword(text) + if (errors.confirmPassword) { + validateConfirmPassword(text) + } + }} + onBlur={() => validateConfirmPassword(confirmPassword)} + secureTextEntry={!showConfirmPassword} + /> + setShowConfirmPassword(!showConfirmPassword)} + > + + + + {errors.confirmPassword && ( + {errors.confirmPassword} + )} + + + } + title="修改密码" + onCancel={onCancel} + onConfirm={handleConfirm} + /> + ) +} + export default observer(function ProfilePage() { const { user } = userStore @@ -126,6 +387,32 @@ export default observer(function ProfilePage() { ) } + const handleChangePassword = () => { + Toast.showModal( + { + try { + // better-auth 使用 changePassword 方法修改密码 + const res = await (authClient as any).changePassword({ + currentPassword, + newPassword, + }) + const err = (res as { error?: { message?: string } }).error + if (err) { + Toast.show({ title: err.message || '修改失败,请重试' }) + return + } + Toast.hideModal() + Toast.show({ title: '密码已修改' }) + } catch (e) { + Toast.show({ title: '修改失败,请重试' }) + } + }} + onCancel={() => Toast.hideModal()} + />, + ) + } + const infoItems: InfoItem[] = [ { id: 'nickname', @@ -133,14 +420,37 @@ export default observer(function ProfilePage() { value: user?.name || '未设置', onPress: handleEditNickname, }, - { - id: 'phone', - label: '手机号', - value: user?.phoneNumber || '', - onPress: () => { - Toast.show({ title: '手机号不可修改' }) - }, - } + ...(user?.emailVerified + ? [ + { + id: 'email', + label: '邮箱', + value: user.email, + onPress: () => { + Toast.show({ title: '邮箱不可修改' }) + }, + }, + { + id: 'password', + label: '密码', + value: '修改密码', + valueGray: true, + onPress: handleChangePassword, + }, + ] + : []), + ...(user?.phoneNumber + ? [ + { + id: 'phone', + label: '手机号', + value: user.phoneNumber, + onPress: () => { + Toast.show({ title: '手机号不可修改' }) + }, + }, + ] + : []), ] const renderHeader = () => ( @@ -199,8 +509,7 @@ export default observer(function ProfilePage() { > {item.value} - {/*手机号 显示隐藏按钮 */} - {item.id !== 'phone' && ( + {item.id !== 'phone' && item.id !== 'email' && ( )} diff --git a/app/settings.tsx b/app/settings.tsx index 40e7647..c02623a 100644 --- a/app/settings.tsx +++ b/app/settings.tsx @@ -107,8 +107,8 @@ export default observer(function SettingsPage() { {user?.name || user?.phoneNumber || '未登录'} - {user?.phoneNumber && user?.name && ( - {user.phoneNumber} + {(user?.phoneNumber || (user?.email && user?.emailVerified)) && user?.name && ( + {user.phoneNumber || user.email} )}