修改昵称
This commit is contained in:
parent
14c84de1ce
commit
9b34b51caf
|
|
@ -71,7 +71,9 @@ const ConfirmModal: React.FC<ConfirmModalProps> = ({
|
||||||
onClick={handleConfirm}
|
onClick={handleConfirm}
|
||||||
>
|
>
|
||||||
<Text className="text-black">{confirmText}</Text>
|
<Text className="text-black">{confirmText}</Text>
|
||||||
<Ionicons color="#000" name="flash" size={16} style={{ marginLeft: 4 }} />
|
{title==='确认支付?' && (
|
||||||
|
<Ionicons color="#000" name="flash" size={16} style={{ marginLeft: 4 }} />
|
||||||
|
)}
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
</Block>
|
</Block>
|
||||||
|
|
|
||||||
|
|
@ -575,7 +575,9 @@ const GooActions = observer<GooActionsProps>(function GooActions({ gooPoints, on
|
||||||
{isDev && isPolling && <Block className="ml-[4px] size-[6px] rounded-full bg-green-500" />}
|
{isDev && isPolling && <Block className="ml-[4px] size-[6px] rounded-full bg-green-500" />}
|
||||||
</Block>
|
</Block>
|
||||||
)}
|
)}
|
||||||
|
<Block onClick={() => router.push('/settings')}>
|
||||||
|
<Ionicons color="white" name="settings-outline" size={22} />
|
||||||
|
</Block>
|
||||||
<Block
|
<Block
|
||||||
className="size-[48px] items-center justify-center rounded-full border-[3px] border-black bg-white shadow-[4px_4px_0px_#000]"
|
className="size-[48px] items-center justify-center rounded-full border-[3px] border-black bg-white shadow-[4px_4px_0px_#000]"
|
||||||
onClick={onOpenSearch}
|
onClick={onOpenSearch}
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ function RootLayout() {
|
||||||
<Stack.Screen name="pointList" options={{ headerShown: false }} />
|
<Stack.Screen name="pointList" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="webview" options={{ headerShown: false }} />
|
<Stack.Screen name="webview" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="scan" options={{ headerShown: false }} />
|
<Stack.Screen name="scan" options={{ headerShown: false }} />
|
||||||
|
<Stack.Screen name="settings" options={{ headerShown: false }} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Providers>
|
</Providers>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,186 @@
|
||||||
|
import { Ionicons } from '@expo/vector-icons'
|
||||||
|
import { Block, ConfirmModal, Img, Input, Text, Toast } from '@share/components'
|
||||||
|
import { router, Stack } from 'expo-router'
|
||||||
|
import { observer } from 'mobx-react-lite'
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { ScrollView } from 'react-native'
|
||||||
|
|
||||||
|
import { authClient } from '@/lib/auth'
|
||||||
|
import { userStore } from '@/stores'
|
||||||
|
|
||||||
|
type InfoItem = {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
valueGray?: boolean
|
||||||
|
onPress: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type EditNicknameModalProps = {
|
||||||
|
initialName: string
|
||||||
|
onConfirm: (name: string) => void | Promise<void>
|
||||||
|
onCancel: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function EditNicknameModal({ initialName, onConfirm, onCancel }: EditNicknameModalProps) {
|
||||||
|
const [name, setName] = useState(initialName)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setName(initialName)
|
||||||
|
}, [initialName])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfirmModal
|
||||||
|
badge="用户名"
|
||||||
|
cancelText="取消"
|
||||||
|
confirmText="确定"
|
||||||
|
content={
|
||||||
|
<Block className="w-full">
|
||||||
|
<Input
|
||||||
|
className="w-full rounded-lg border-2 border-black px-[12px] py-[10px] text-[14px]"
|
||||||
|
placeholder="请输入6-12个字符"
|
||||||
|
placeholderTextColor="#9CA3AF"
|
||||||
|
value={name}
|
||||||
|
onChangeText={setName}
|
||||||
|
/>
|
||||||
|
</Block>
|
||||||
|
}
|
||||||
|
title="修改昵称"
|
||||||
|
onCancel={onCancel}
|
||||||
|
onConfirm={() => onConfirm(name)}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(function ProfilePage() {
|
||||||
|
const { user } = userStore
|
||||||
|
|
||||||
|
const handleEditAvatar = () => {
|
||||||
|
// TODO: 打开相册/拍照更换头像
|
||||||
|
Toast.show({ title: '更换头像功能开发中' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleEditNickname = () => {
|
||||||
|
Toast.showModal(
|
||||||
|
<EditNicknameModal
|
||||||
|
initialName={user?.name || ''}
|
||||||
|
onConfirm={async (name) => {
|
||||||
|
const trimmed = name.trim()
|
||||||
|
if (trimmed.length < 6 || trimmed.length > 12) {
|
||||||
|
Toast.show({ title: '请输入6-12个字符' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const res = await authClient.updateUser({ name: trimmed })
|
||||||
|
const err = (res as { error?: { message?: string } }).error
|
||||||
|
if (err) {
|
||||||
|
Toast.show({ title: err.message || '修改失败,请重试' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (userStore.user) {
|
||||||
|
userStore.setUser({ ...userStore.user, name: trimmed })
|
||||||
|
}
|
||||||
|
Toast.hideModal()
|
||||||
|
Toast.show({ title: '昵称已更新' })
|
||||||
|
} catch (e) {
|
||||||
|
Toast.show({ title: '修改失败,请重试' })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCancel={() => Toast.hideModal()}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const infoItems: InfoItem[] = [
|
||||||
|
{
|
||||||
|
id: 'nickname',
|
||||||
|
label: '昵称',
|
||||||
|
value: user?.name || '未设置',
|
||||||
|
onPress: handleEditNickname,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'phone',
|
||||||
|
label: '手机号',
|
||||||
|
value: user?.phoneNumber || '',
|
||||||
|
onPress: () => {
|
||||||
|
Toast.show({ title: '手机号不可修改' })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const renderHeader = () => (
|
||||||
|
<Block className="flex-row items-center justify-between px-[16px] py-[10px]">
|
||||||
|
<Block
|
||||||
|
className="ml-[-8px] size-[40px] items-center justify-center"
|
||||||
|
opacity={0.7}
|
||||||
|
onClick={() => router.back()}
|
||||||
|
>
|
||||||
|
<Ionicons color="black" name="chevron-back" size={24} />
|
||||||
|
</Block>
|
||||||
|
<Text className="text-[16px] font-[700] text-black">个人信息</Text>
|
||||||
|
<Block className="w-[32px]" />
|
||||||
|
</Block>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderAvatarSection = () => (
|
||||||
|
<Block className="mt-[24px] items-center">
|
||||||
|
<Block className="relative size-[100px] overflow-hidden rounded-full">
|
||||||
|
<Block className="size-[100px] items-center justify-center overflow-hidden rounded-full bg-gray-200">
|
||||||
|
{user?.image ? (
|
||||||
|
<Img
|
||||||
|
src={user.image}
|
||||||
|
style={{ width: 100, height: 100, borderRadius: 50 }}
|
||||||
|
width={100}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Ionicons color="#9CA3AF" name="person" size={48} />
|
||||||
|
)}
|
||||||
|
</Block>
|
||||||
|
<Block
|
||||||
|
className="absolute bottom-0 left-0 right-0 items-center justify-center bg-[#00000080] py-[2px]"
|
||||||
|
style={{ borderBottomLeftRadius: 50, borderBottomRightRadius: 50 }}
|
||||||
|
onClick={handleEditAvatar}
|
||||||
|
>
|
||||||
|
<Text className="text-[12px] font-[600] text-white">编辑</Text>
|
||||||
|
</Block>
|
||||||
|
</Block>
|
||||||
|
</Block>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderInfoList = () => (
|
||||||
|
<Block className="mt-[32px] px-[16px]">
|
||||||
|
{infoItems.map((item) => (
|
||||||
|
<Block
|
||||||
|
key={item.id}
|
||||||
|
className="flex-row items-center gap-[12px] py-[16px]"
|
||||||
|
onClick={item.onPress}
|
||||||
|
>
|
||||||
|
<Text className="w-[56px] text-[14px] text-black">{item.label}</Text>
|
||||||
|
<Text
|
||||||
|
className="flex-1 text-[14px] text-right"
|
||||||
|
style={{ color: item.valueGray ? '#9CA3AF' : '#000' }}
|
||||||
|
numberOfLines={1}
|
||||||
|
ellipsizeMode="tail"
|
||||||
|
>
|
||||||
|
{item.value}
|
||||||
|
</Text>
|
||||||
|
{/*手机号 显示隐藏按钮 */}
|
||||||
|
{item.id !== 'phone' && (
|
||||||
|
<Ionicons color="#9CA3AF" name="chevron-forward" size={18} />
|
||||||
|
)}
|
||||||
|
</Block>
|
||||||
|
))}
|
||||||
|
</Block>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Block className="h-full flex-1 bg-white">
|
||||||
|
<Stack.Screen options={{ headerShown: false }} />
|
||||||
|
{renderHeader()}
|
||||||
|
<ScrollView contentContainerStyle={{ flexGrow: 1 }} showsVerticalScrollIndicator={false}>
|
||||||
|
{renderAvatarSection()}
|
||||||
|
{renderInfoList()}
|
||||||
|
</ScrollView>
|
||||||
|
</Block>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,165 @@
|
||||||
|
import { Ionicons } from '@expo/vector-icons'
|
||||||
|
import { Block, Img, Text } from '@share/components'
|
||||||
|
import { router, Stack } from 'expo-router'
|
||||||
|
import { observer } from 'mobx-react-lite'
|
||||||
|
import React from 'react'
|
||||||
|
import { ScrollView } from 'react-native'
|
||||||
|
|
||||||
|
import { ConfirmModal, Toast } from '@share/components'
|
||||||
|
import { userStore } from '@/stores'
|
||||||
|
|
||||||
|
type MenuItem = {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
icon: keyof typeof Ionicons.glyphMap
|
||||||
|
onPress: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default observer(function SettingsPage() {
|
||||||
|
const { user, isAuthenticated, signOut } = userStore
|
||||||
|
|
||||||
|
const handleLogout = () => {
|
||||||
|
Toast.showModal(
|
||||||
|
<ConfirmModal
|
||||||
|
title="退出登录"
|
||||||
|
content="确定要退出登录吗?"
|
||||||
|
onCancel={Toast.hideModal}
|
||||||
|
|
||||||
|
onConfirm={async () => {
|
||||||
|
await signOut()
|
||||||
|
Toast.show({ title: '已退出登录' })
|
||||||
|
router.replace('/(tabs)')
|
||||||
|
// 关闭modal
|
||||||
|
Toast.hideModal()
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuItems: MenuItem[] = [
|
||||||
|
{
|
||||||
|
id: 'service',
|
||||||
|
label: '服务条款',
|
||||||
|
icon: 'document-text-outline',
|
||||||
|
onPress: () => {
|
||||||
|
router.push({
|
||||||
|
pathname: '/webview',
|
||||||
|
params: { url: 'https://mixvideo.bowong.cc/terms', title: '服务条款' },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'privacy',
|
||||||
|
label: '隐私协议',
|
||||||
|
icon: 'shield-outline',
|
||||||
|
onPress: () => {
|
||||||
|
router.push({
|
||||||
|
pathname: '/webview',
|
||||||
|
params: { url: 'https://mixvideo.bowong.cc/privacy', title: '隐私协议' },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// id: 'about',
|
||||||
|
// label: '关于我们',
|
||||||
|
// icon: 'information-circle-outline',
|
||||||
|
// onPress: () => {
|
||||||
|
// // TODO: 替换为实际的关于我们URL或创建关于页面
|
||||||
|
// router.push({
|
||||||
|
// pathname: '/webview',
|
||||||
|
// params: { url: 'https://example.com/about', title: '关于我们' },
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleUserProfileClick = () => {
|
||||||
|
router.push('/profile')
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderHeader = () => (
|
||||||
|
<Block
|
||||||
|
className="flex-row items-center justify-between px-[16px] py-[10px]"
|
||||||
|
>
|
||||||
|
<Block className="ml-[-8px] size-[40px] items-center justify-center" opacity={0.7} onClick={() => router.back()}>
|
||||||
|
<Ionicons color="black" name="chevron-back" size={24} />
|
||||||
|
</Block>
|
||||||
|
<Text className="text-[16px] font-[700] text-black">设置</Text>
|
||||||
|
<Block className="w-[32px]" />
|
||||||
|
</Block>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderUserSection = () => (
|
||||||
|
<Block
|
||||||
|
className="flex-row items-center gap-[12px] px-[16px] py-[10px]"
|
||||||
|
onClick={handleUserProfileClick}
|
||||||
|
>
|
||||||
|
<Block className="size-[60px] items-center justify-center overflow-hidden rounded-full bg-gray-200">
|
||||||
|
{user?.image ? (
|
||||||
|
<Img
|
||||||
|
src={user.image}
|
||||||
|
style={{ width: 60, height: 60, borderRadius: 30 }}
|
||||||
|
width={60}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Ionicons color="#9CA3AF" name="person" size={32} />
|
||||||
|
)}
|
||||||
|
</Block>
|
||||||
|
<Block className="flex-1">
|
||||||
|
<Text className="text-[16px] font-[700] text-black">{user?.name || user?.email || '未登录'}</Text>
|
||||||
|
{user?.email && user?.name && (
|
||||||
|
<Text className="mt-[4px] text-[12px] text-gray-500">{user.email}</Text>
|
||||||
|
)}
|
||||||
|
</Block>
|
||||||
|
<Ionicons color="#9CA3AF" name="chevron-forward" size={20} />
|
||||||
|
</Block>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderMenuSection = () => (
|
||||||
|
<Block className="mt-[8px]">
|
||||||
|
<Text className="px-[16px] py-[8px] text-[12px] text-gray-400">使用相关</Text>
|
||||||
|
<Block className="bg-white">
|
||||||
|
{menuItems.map((item, index) => (
|
||||||
|
<Block key={item.id}>
|
||||||
|
<Block
|
||||||
|
className="flex-row items-center gap-[12px] px-[16px] py-[16px]"
|
||||||
|
onClick={item.onPress}
|
||||||
|
>
|
||||||
|
<Ionicons color="#666" name={item.icon} size={22} />
|
||||||
|
<Text className="flex-1 text-[14px] text-black">{item.label}</Text>
|
||||||
|
<Ionicons color="#9CA3AF" name="chevron-forward" size={20} />
|
||||||
|
</Block>
|
||||||
|
{index < menuItems.length - 1 && (
|
||||||
|
<Block className="ml-[50px] h-[1px] bg-gray-100" />
|
||||||
|
)}
|
||||||
|
</Block>
|
||||||
|
))}
|
||||||
|
</Block>
|
||||||
|
</Block>
|
||||||
|
)
|
||||||
|
|
||||||
|
const renderLogoutButton = () => (
|
||||||
|
<Block className="mt-[32px] items-center px-[16px]">
|
||||||
|
{isAuthenticated && (
|
||||||
|
<Block
|
||||||
|
className="w-full max-w-[200px] items-center justify-center rounded-lg bg-gray-100 py-[14px]"
|
||||||
|
onClick={handleLogout}
|
||||||
|
>
|
||||||
|
<Text className="text-[14px] font-[600] text-black">退出登录</Text>
|
||||||
|
</Block>
|
||||||
|
)}
|
||||||
|
</Block>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Block className="flex-1 bg-white h-full">
|
||||||
|
<Stack.Screen options={{ headerShown: false }} />
|
||||||
|
{renderHeader()}
|
||||||
|
<ScrollView contentContainerStyle={{ flexGrow: 1 }} showsVerticalScrollIndicator={false}>
|
||||||
|
{renderUserSection()}
|
||||||
|
{renderMenuSection()}
|
||||||
|
{renderLogoutButton()}
|
||||||
|
</ScrollView>
|
||||||
|
</Block>
|
||||||
|
)
|
||||||
|
})
|
||||||
Loading…
Reference in New Issue