u
This commit is contained in:
1 parent
2efeb7cee2
commit
c0531c1d0a
35 files changed
+1817
-984
No files matched your search
@@ -1,12 +1,12 @@
|
||||
import type { UserSpace } from '@/types/space'
|
||||
import { useFocusEffect, useRouter } from 'expo-router'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Pressable, Text, View } from 'react-native'
|
||||
import { ActivityIndicator, Pressable, RefreshControl, Text, View } from 'react-native'
|
||||
import { KeyboardAvoidingView } from 'react-native-keyboard-controller'
|
||||
import Animated, { useAnimatedRef } from 'react-native-reanimated'
|
||||
import Sortable from 'react-native-sortables'
|
||||
import { toast } from 'sonner-native'
|
||||
import { AButton, ADivider, AInput, LucideIcon } from '@/components/common/ao'
|
||||
import { AButton, ADivider, AInput, AModal, LucideIcon } from '@/components/common/ao'
|
||||
import EmptyState from '@/components/common/EmptyStatus'
|
||||
import CustomHeader from '@/components/custom/Header'
|
||||
import {
|
||||
@@ -17,11 +17,13 @@ import {
|
||||
ActionsheetDragIndicatorWrapper,
|
||||
} from '@/components/ui/actionsheet'
|
||||
import { Badge, BadgeText } from '@/components/ui/badge'
|
||||
import { createSpaceApi, listUserSpacesApi, updateSpaceSortNumberApi } from '@/request/api/space/index'
|
||||
import { createSpaceApi, joinSpaceApi, listUserSpacesApi, updateSpaceSortNumberApi } from '@/request/api/space/index'
|
||||
import { useSpaceStore } from '@/store/space'
|
||||
import { useUserStore } from '@/store/user'
|
||||
|
||||
export default function AuthSpace() {
|
||||
const userStore = useUserStore()
|
||||
const spaceStore = useSpaceStore()
|
||||
const router = useRouter()
|
||||
|
||||
// scrollView 相关
|
||||
@@ -29,6 +31,9 @@ export default function AuthSpace() {
|
||||
const [scrollHeight, setScrollHeight] = useState(0)
|
||||
const ITEM_HEIGHT = 64
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [refreshLoading, setRefreshLoading] = useState(false)
|
||||
|
||||
// 空间列表
|
||||
const [spaces, setSpaces] = useState<UserSpace[]>([])
|
||||
|
||||
@@ -51,11 +56,25 @@ export default function AuthSpace() {
|
||||
}
|
||||
}, [createOpen])
|
||||
|
||||
// 加入
|
||||
const [showJoin, setShowJoin] = useState(false)
|
||||
const [joinLoading, setJoinLoading] = useState(false)
|
||||
const [joinForm, setJoinForm] = useState({
|
||||
invite_code: '',
|
||||
})
|
||||
|
||||
// 获取空间列表
|
||||
function listUserSpaces() {
|
||||
function listUserSpaces(loading = true) {
|
||||
if (loading) {
|
||||
setLoading(true)
|
||||
}
|
||||
|
||||
listUserSpacesApi().then((res) => {
|
||||
if (res.isSuccess) {
|
||||
setSpaces(res.data.spaces)
|
||||
if (loading) {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -105,6 +124,14 @@ export default function AuthSpace() {
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
item.space_id === spaceStore?.currentSpace?.space_id && (
|
||||
<Badge size="sm" className="bg-[#333] rounded-full">
|
||||
<BadgeText className="text-white">当前空间</BadgeText>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
</View>
|
||||
|
||||
<View className="flex-row items-center gap-3 mt-2">
|
||||
@@ -161,7 +188,7 @@ export default function AuthSpace() {
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-[#f5f6f8]">
|
||||
<CustomHeader title="空间列表" />
|
||||
<CustomHeader title="空间管理" />
|
||||
|
||||
<View className="flex-1 p-4">
|
||||
<View className="">
|
||||
@@ -182,6 +209,9 @@ export default function AuthSpace() {
|
||||
className="flex-1 rounded-full"
|
||||
size="sm"
|
||||
leftIcon={<LucideIcon icon="HousePlus" />}
|
||||
onPress={() => {
|
||||
setShowJoin(true)
|
||||
}}
|
||||
>
|
||||
加入其他空间
|
||||
</AButton>
|
||||
@@ -196,52 +226,69 @@ export default function AuthSpace() {
|
||||
onLayout={(e) => {
|
||||
setScrollHeight(e.nativeEvent.layout.height)
|
||||
}}
|
||||
refreshControl={(
|
||||
<RefreshControl
|
||||
refreshing={refreshLoading}
|
||||
onRefresh={async () => {
|
||||
setRefreshLoading(true)
|
||||
await listUserSpaces(false)
|
||||
setRefreshLoading(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{spaces.length === 0
|
||||
? (
|
||||
<View className="flex-1 items-center justify-center mt-12">
|
||||
<EmptyState
|
||||
title="暂无空间"
|
||||
description="请选择 创建 或 加入 空间"
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
: (
|
||||
<Sortable.Grid
|
||||
scrollableRef={scrollableRef}
|
||||
keyExtractor={(item: UserSpace) => {
|
||||
return String(item.space_id)
|
||||
}}
|
||||
activeItemScale={1}
|
||||
columns={1}
|
||||
data={spaces}
|
||||
overDrag="vertical"
|
||||
renderItem={renderItem}
|
||||
rowGap={10}
|
||||
highlight-next-line
|
||||
customHandle
|
||||
autoScrollActivationOffset={200}
|
||||
onDragEnd={(dragData) => {
|
||||
const newData = dragData.data
|
||||
if (JSON.stringify(spaces) !== JSON.stringify(newData)) {
|
||||
const updateData = newData.map((item, index) => {
|
||||
return {
|
||||
space_id: item.space_id,
|
||||
sort_number: index,
|
||||
}
|
||||
})
|
||||
{
|
||||
loading ? (
|
||||
<View className="flex-1 items-center justify-center">
|
||||
<ActivityIndicator size="large" color="#333" />
|
||||
</View>
|
||||
)
|
||||
: spaces.length === 0
|
||||
? (
|
||||
<View className="flex-1 items-center justify-center mt-12">
|
||||
<EmptyState
|
||||
title="暂无空间"
|
||||
description="请选择 创建 或 加入 空间"
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<Sortable.Grid
|
||||
scrollableRef={scrollableRef}
|
||||
keyExtractor={(item: UserSpace) => {
|
||||
return String(item.space_id)
|
||||
}}
|
||||
activeItemScale={1}
|
||||
columns={1}
|
||||
data={spaces}
|
||||
overDrag="vertical"
|
||||
renderItem={renderItem}
|
||||
rowGap={10}
|
||||
highlight-next-line
|
||||
customHandle
|
||||
autoScrollActivationOffset={200}
|
||||
onDragEnd={(dragData) => {
|
||||
const newData = dragData.data
|
||||
if (JSON.stringify(spaces) !== JSON.stringify(newData)) {
|
||||
const updateData = newData.map((item, index) => {
|
||||
return {
|
||||
space_id: item.space_id,
|
||||
sort_number: index,
|
||||
}
|
||||
})
|
||||
|
||||
updateSpaceSortNumberApi({
|
||||
spaces: updateData,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
// 更新成功
|
||||
updateSpaceSortNumberApi({
|
||||
spaces: updateData,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
// 更新成功
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
</Animated.ScrollView>
|
||||
</View>
|
||||
|
||||
@@ -323,6 +370,84 @@ export default function AuthSpace() {
|
||||
</ActionsheetContent>
|
||||
</KeyboardAvoidingView>
|
||||
</Actionsheet>
|
||||
|
||||
<AModal
|
||||
title="加入空间"
|
||||
isOpen={showJoin}
|
||||
onClose={() => {
|
||||
setShowJoin(false)
|
||||
|
||||
setTimeout(() => {
|
||||
setJoinForm({
|
||||
invite_code: '',
|
||||
})
|
||||
}, 300)
|
||||
}}
|
||||
body={(
|
||||
<View className="px-3">
|
||||
<AInput
|
||||
placeholder="请输入空间邀请码"
|
||||
value={joinForm.invite_code}
|
||||
onChangeText={(text) => {
|
||||
setJoinForm({
|
||||
invite_code: text,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
footer={(
|
||||
<View className="w-full px-3 pb-3">
|
||||
<AButton
|
||||
loading={joinLoading}
|
||||
onPress={async () => {
|
||||
if (joinForm.invite_code === '') {
|
||||
toast.warning('邀请码不能为空')
|
||||
return
|
||||
}
|
||||
setJoinLoading(true)
|
||||
|
||||
try {
|
||||
await joinSpaceApi({
|
||||
invite_code: joinForm.invite_code,
|
||||
member_name: userStore.userInfo.nickname,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
toast.success('加入成功')
|
||||
setShowJoin(false)
|
||||
|
||||
setTimeout(() => {
|
||||
setJoinForm({
|
||||
invite_code: '',
|
||||
})
|
||||
|
||||
listUserSpaces()
|
||||
}, 300)
|
||||
}
|
||||
})
|
||||
}
|
||||
finally {
|
||||
setJoinLoading(false)
|
||||
}
|
||||
}}
|
||||
|
||||
>
|
||||
加入
|
||||
</AButton>
|
||||
<ADivider text="或" />
|
||||
<AButton
|
||||
loading={joinLoading}
|
||||
|
||||
variant="outline"
|
||||
onPress={() => {
|
||||
toast.warning('功能待实现,敬请期待!')
|
||||
}}
|
||||
>
|
||||
扫码加入
|
||||
</AButton>
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import type { UserSpace } from '@/types/space'
|
||||
import * as Clipboard from 'expo-clipboard'
|
||||
import { useLocalSearchParams } from 'expo-router'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useFocusEffect, useLocalSearchParams, useRouter } from 'expo-router'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Modal,
|
||||
Pressable,
|
||||
Text,
|
||||
@@ -12,69 +14,159 @@ import QRCode from 'react-native-qrcode-svg'
|
||||
import { toast } from 'sonner-native'
|
||||
import { AButton, ADivider, LucideIcon } from '@/components/common/ao'
|
||||
import CustomHeader from '@/components/custom/Header'
|
||||
import { getSpaceSettingsApi, refreshInviteCodeApi } from '@/request/api/space'
|
||||
import { useUserStore } from '@/store/user'
|
||||
|
||||
export default function Invite() {
|
||||
const params = useLocalSearchParams<{ invite_code: string }>()
|
||||
const { invite_code } = params
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const params = useLocalSearchParams<{ space_id: string }>()
|
||||
const { space_id } = params
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [refreshLoading, setRefreshLoading] = useState(false)
|
||||
|
||||
const [space, setSpace] = useState<UserSpace | null>(null)
|
||||
|
||||
const [qrcode, setQrcode] = useState('space:')
|
||||
|
||||
const [previewVisible, setPreviewVisible] = useState(false)
|
||||
|
||||
async function copyInviteCode() {
|
||||
await Clipboard.setStringAsync(invite_code)
|
||||
toast.success('邀请码已复制')
|
||||
if (space) {
|
||||
await Clipboard.setStringAsync(space.invite_code)
|
||||
toast.success('邀请码已复制')
|
||||
}
|
||||
else {
|
||||
toast.error('邀请码无效')
|
||||
}
|
||||
}
|
||||
|
||||
async function getSpace() {
|
||||
setLoading(true)
|
||||
await getSpaceSettingsApi({
|
||||
space_id: Number(space_id),
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
setSpace(res.data)
|
||||
setLoading(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setQrcode(`space:${invite_code}`)
|
||||
console.log(`space:${invite_code}`)
|
||||
}, [invite_code])
|
||||
if (space) {
|
||||
setQrcode(`space:${space.invite_code}`)
|
||||
}
|
||||
}, [space])
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
getSpace()
|
||||
}, []),
|
||||
)
|
||||
|
||||
function refreshInviteCode() {
|
||||
if (!space) {
|
||||
toast.error('空间无效')
|
||||
router.back()
|
||||
return
|
||||
}
|
||||
|
||||
setRefreshLoading(true)
|
||||
|
||||
refreshInviteCodeApi({
|
||||
space_id: space.space_id,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
setSpace(prev =>
|
||||
prev
|
||||
? {
|
||||
...prev,
|
||||
invite_code: res.data.invite_code,
|
||||
}
|
||||
: prev,
|
||||
)
|
||||
|
||||
setRefreshLoading(false)
|
||||
|
||||
toast.success('刷新成功')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<View className="flex-1 bg-[#f5f6f8]">
|
||||
<CustomHeader title="分享空间" />
|
||||
|
||||
<View className="px-5 pt-6">
|
||||
{
|
||||
loading ? (
|
||||
<View className="flex-1 items-center justify-center">
|
||||
<ActivityIndicator size="large" color="#333" />
|
||||
</View>
|
||||
) : (
|
||||
<View className="px-5 pt-6">
|
||||
|
||||
{/* 邀请码卡片 */}
|
||||
<View className="bg-white rounded-2xl p-6 items-center">
|
||||
<Text className="text-gray-500 text-sm mb-3">
|
||||
复制邀请码邀请家庭成员加入
|
||||
</Text>
|
||||
{/* 邀请码卡片 */}
|
||||
<View className="bg-white rounded-2xl p-6 items-center">
|
||||
<Text className="text-gray-500 text-sm mb-3">
|
||||
复制邀请码邀请家庭成员加入
|
||||
</Text>
|
||||
|
||||
<Text className="text-4xl font-bold tracking-[8px] text-gray-900 mb-4">
|
||||
{invite_code}
|
||||
</Text>
|
||||
<Text className="text-4xl font-bold tracking-[8px] text-gray-900 mb-4">
|
||||
{space?.invite_code}
|
||||
</Text>
|
||||
|
||||
<AButton
|
||||
onPress={copyInviteCode}
|
||||
className="rounded-full"
|
||||
leftIcon={
|
||||
<LucideIcon icon="Copy" size={16} color="#fff" clickable={false} />
|
||||
}
|
||||
>
|
||||
复制邀请码
|
||||
</AButton>
|
||||
</View>
|
||||
<View className="flex-row gap-3">
|
||||
<AButton
|
||||
onPress={copyInviteCode}
|
||||
className="rounded-full flex-1"
|
||||
leftIcon={
|
||||
<LucideIcon icon="Copy" size={16} color="#fff" clickable={false} />
|
||||
}
|
||||
>
|
||||
复制邀请码
|
||||
</AButton>
|
||||
|
||||
<View className="my-8">
|
||||
<ADivider direction="horizontal" text="或扫描二维码" />
|
||||
</View>
|
||||
{
|
||||
userStore.userInfo.id === space?.owner_id && (
|
||||
<AButton
|
||||
variant="outline"
|
||||
className="rounded-full px-4"
|
||||
leftIcon={
|
||||
<LucideIcon icon="RefreshCw" size={16} clickable={false} />
|
||||
}
|
||||
loading={refreshLoading}
|
||||
onPress={refreshInviteCode}
|
||||
>
|
||||
刷新
|
||||
</AButton>
|
||||
)
|
||||
}
|
||||
|
||||
{/* 二维码卡片 */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={() => setPreviewVisible(true)}
|
||||
className="bg-white rounded-2xl py-8 items-center"
|
||||
>
|
||||
<QRCode
|
||||
size={160}
|
||||
value={qrcode || ''}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
</View>
|
||||
<View className="my-8">
|
||||
<ADivider direction="horizontal" text="或扫描二维码" />
|
||||
</View>
|
||||
|
||||
{/* 二维码卡片 */}
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={() => setPreviewVisible(true)}
|
||||
className="bg-white rounded-2xl py-8 items-center"
|
||||
>
|
||||
<QRCode
|
||||
size={160}
|
||||
value={qrcode || ''}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
{/* 全屏预览(无动画) */}
|
||||
<Modal
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { View } from 'react-native'
|
||||
import CustomHeader from '@/components/custom/Header'
|
||||
|
||||
export default function () {
|
||||
return (
|
||||
<View className="flex-1">
|
||||
<CustomHeader title="成员管理" />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
import type { SpaceMember, UserSpace } from '@/types/space'
|
||||
import { useFocusEffect, useLocalSearchParams } from 'expo-router'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { ActivityIndicator, RefreshControl, ScrollView, Text, TouchableOpacity, View } from 'react-native'
|
||||
import { toast } from 'sonner-native'
|
||||
import { ADivider, AModal, LucideIcon } from '@/components/common/ao'
|
||||
import EmptyState from '@/components/common/EmptyStatus'
|
||||
import CustomHeader from '@/components/custom/Header'
|
||||
import {
|
||||
Actionsheet,
|
||||
ActionsheetBackdrop,
|
||||
ActionsheetContent,
|
||||
ActionsheetDragIndicator,
|
||||
ActionsheetDragIndicatorWrapper,
|
||||
ActionsheetItem,
|
||||
ActionsheetItemText,
|
||||
} from '@/components/ui/actionsheet'
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallbackText,
|
||||
AvatarImage,
|
||||
} from '@/components/ui/avatar'
|
||||
import { Badge, BadgeText } from '@/components/ui/badge'
|
||||
import { getSpaceSettingsApi, kickoutSpaceMemberApi, listSpaceMembersApi, transferSpaceApi } from '@/request/api/space'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { formatRelativeTime } from '@/utils/time'
|
||||
|
||||
export default function () {
|
||||
const userStore = useUserStore()
|
||||
|
||||
const params = useLocalSearchParams<{ space_id: string }>()
|
||||
const { space_id } = params
|
||||
|
||||
const [space, setSpace] = useState<UserSpace | null>(null)
|
||||
const [members, setMembers] = useState<SpaceMember[]>([])
|
||||
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [refreshLoading, setRefreshLoading] = useState(false)
|
||||
|
||||
const [showAction, setShowAction] = useState(false)
|
||||
const [currentMember, setCurrentMember] = useState<SpaceMember | null>(null)
|
||||
|
||||
// 踢出空间
|
||||
const [showKickout, setShowKickout] = useState(false)
|
||||
// 转让空间
|
||||
const [showTransfer, setShowTransfer] = useState(false)
|
||||
|
||||
async function listSpaceMembers() {
|
||||
await listSpaceMembersApi({
|
||||
space_id: Number(space_id),
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
setMembers(res.data.members)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function getSpace() {
|
||||
await getSpaceSettingsApi({
|
||||
space_id: Number(space_id),
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
setSpace(res.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function loadData(loading = true) {
|
||||
if (loading) {
|
||||
setLoading(true)
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
getSpace(),
|
||||
listSpaceMembers(),
|
||||
])
|
||||
|
||||
if (loading) {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (space_id) {
|
||||
loadData()
|
||||
}
|
||||
}, []),
|
||||
)
|
||||
|
||||
return (
|
||||
<View className="flex-1 ">
|
||||
<CustomHeader
|
||||
title="空间成员"
|
||||
/>
|
||||
|
||||
<View className="flex-1 bg-[#f5f6f8] relative">
|
||||
{
|
||||
loading
|
||||
? (
|
||||
<View className="flex-1 items-center justify-center">
|
||||
<ActivityIndicator size="large" color="#333" />
|
||||
</View>
|
||||
)
|
||||
: space === null
|
||||
? (
|
||||
<EmptyState title="空间不存在或已解散" />
|
||||
)
|
||||
: (
|
||||
<>
|
||||
<View className="pt-6 pb-24 px-4 bg-[#333] items-center">
|
||||
<Text className="text-lg text-white font-semibold">
|
||||
{space.name || ''}
|
||||
</Text>
|
||||
|
||||
<View className="flex-row gap-2 mt-3">
|
||||
{space.is_default === 1 && (
|
||||
<Badge className="rounded-full bg-[#515151] px-3">
|
||||
<BadgeText className="text-white">默认</BadgeText>
|
||||
</Badge>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="mt-[-60] p-6 bg-white h-full rounded-tl-2xl rounded-tr-2xl">
|
||||
<View className="flex-row items-center justify-between">
|
||||
<Text className="text-gray-600 font-semibold">
|
||||
空间成员
|
||||
</Text>
|
||||
<Text>
|
||||
{members.length}
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<ScrollView
|
||||
className="pt-4"
|
||||
showsVerticalScrollIndicator={false}
|
||||
showsHorizontalScrollIndicator={false}
|
||||
refreshControl={(
|
||||
<RefreshControl
|
||||
refreshing={refreshLoading}
|
||||
onRefresh={async () => {
|
||||
setRefreshLoading(true)
|
||||
|
||||
await loadData(false)
|
||||
|
||||
setRefreshLoading(false)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{
|
||||
members.map((item) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
className="flex-row justify-between items-center pb-4"
|
||||
key={item.user_id}
|
||||
onPress={() => {
|
||||
setCurrentMember(item)
|
||||
setShowAction(true)
|
||||
}}
|
||||
>
|
||||
<View className="flex-row items-center">
|
||||
<Avatar size="md">
|
||||
<AvatarFallbackText>{item.member_name}</AvatarFallbackText>
|
||||
<AvatarImage
|
||||
source={{
|
||||
uri: item.avatar,
|
||||
}}
|
||||
/>
|
||||
</Avatar>
|
||||
<View className="ml-3">
|
||||
<Text className="text-sm">{item.member_name}</Text>
|
||||
<View className="mt-2 gap-2 flex-row items-center">
|
||||
<View className="flex-row gap-1 items-center">
|
||||
<LucideIcon icon="Clock" size={12} color="#6b7280" clickable={false} />
|
||||
<Text className="text-xs text-gray-500">
|
||||
{formatRelativeTime(item.last_login_time)}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="flex-row gap-2">
|
||||
{
|
||||
item.role === 0 && (
|
||||
<Badge size="sm" className="rounded-full mt-1">
|
||||
<BadgeText>创建人</BadgeText>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
item.member_status === 1 && (
|
||||
<Badge size="sm" className="rounded-full mt-1" action="error">
|
||||
<BadgeText>禁用</BadgeText>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
})
|
||||
}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</View>
|
||||
|
||||
<Actionsheet
|
||||
isOpen={showAction}
|
||||
onClose={() => {
|
||||
setShowAction(false)
|
||||
setTimeout(() => {
|
||||
setCurrentMember(null)
|
||||
}, 300)
|
||||
}}
|
||||
>
|
||||
<ActionsheetBackdrop />
|
||||
<ActionsheetContent style={{
|
||||
paddingBottom: 16,
|
||||
}}
|
||||
>
|
||||
|
||||
<ActionsheetDragIndicatorWrapper>
|
||||
<ActionsheetDragIndicator />
|
||||
</ActionsheetDragIndicatorWrapper>
|
||||
|
||||
<ActionsheetItem
|
||||
onPress={() => {
|
||||
toast.warning('功能待开发,敬请期待!')
|
||||
}}
|
||||
>
|
||||
<LucideIcon icon="SquareUserRound" clickable={false} color="#333" />
|
||||
<ActionsheetItemText>查看资料</ActionsheetItemText>
|
||||
</ActionsheetItem>
|
||||
|
||||
{
|
||||
currentMember?.user_id !== userStore.userInfo.id && (
|
||||
<ActionsheetItem
|
||||
onPress={() => {
|
||||
toast.warning('功能待开发,敬请期待!')
|
||||
}}
|
||||
>
|
||||
<LucideIcon icon="UserPen" clickable={false} color="#333" />
|
||||
<ActionsheetItemText>修改备注</ActionsheetItemText>
|
||||
</ActionsheetItem>
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
space?.owner_id === userStore.userInfo.id
|
||||
&& currentMember?.user_id !== userStore.userInfo.id && (
|
||||
<>
|
||||
<ADivider />
|
||||
|
||||
<ActionsheetItem
|
||||
onPress={() => {
|
||||
setShowTransfer(true)
|
||||
}}
|
||||
>
|
||||
<LucideIcon icon="Rotate3d" clickable={false} color="#333" />
|
||||
<ActionsheetItemText>转让空间</ActionsheetItemText>
|
||||
</ActionsheetItem>
|
||||
|
||||
<ActionsheetItem
|
||||
onPress={() => {
|
||||
toast.warning('功能待开发,敬请期待!')
|
||||
}}
|
||||
>
|
||||
<LucideIcon icon="ShieldAlert" clickable={false} color="#333" />
|
||||
<ActionsheetItemText>禁用</ActionsheetItemText>
|
||||
</ActionsheetItem>
|
||||
|
||||
<ADivider />
|
||||
|
||||
<ActionsheetItem
|
||||
onPress={() => {
|
||||
setShowKickout(true)
|
||||
}}
|
||||
>
|
||||
<LucideIcon icon="CircleX" clickable={false} color="#333" />
|
||||
<ActionsheetItemText>踢出空间</ActionsheetItemText>
|
||||
</ActionsheetItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</ActionsheetContent>
|
||||
</Actionsheet>
|
||||
|
||||
<AModal
|
||||
isOpen={showKickout}
|
||||
title="提示"
|
||||
body="确定该用户踢出空间?"
|
||||
onCancel={() => {
|
||||
setShowKickout(false)
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowKickout(false)
|
||||
}}
|
||||
onConfirm={async () => {
|
||||
if (!currentMember) {
|
||||
toast.warning('选中成员无效')
|
||||
return
|
||||
}
|
||||
|
||||
await kickoutSpaceMemberApi({
|
||||
space_id: currentMember.space_id,
|
||||
member_id: currentMember.user_id,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
toast.success('已将该用户踢出空间')
|
||||
|
||||
setShowKickout(false)
|
||||
|
||||
setShowAction(false)
|
||||
|
||||
setTimeout(() => {
|
||||
setCurrentMember(null)
|
||||
|
||||
loadData()
|
||||
}, 300)
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
|
||||
<AModal
|
||||
isOpen={showTransfer}
|
||||
title="提示"
|
||||
body={`确定将该空间的所有权转让给 ${currentMember?.member_name || '未知用户'} ?`}
|
||||
onCancel={() => {
|
||||
setShowTransfer(false)
|
||||
}}
|
||||
onClose={() => {
|
||||
setShowTransfer(false)
|
||||
}}
|
||||
onConfirm={async () => {
|
||||
if (!currentMember) {
|
||||
toast.warning('选中成员无效')
|
||||
return
|
||||
}
|
||||
|
||||
await transferSpaceApi({
|
||||
space_id: currentMember.space_id,
|
||||
member_id: currentMember.user_id,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
toast.success('已成功转让空间')
|
||||
|
||||
setSpace((val) => {
|
||||
if (!val)
|
||||
return val
|
||||
|
||||
return {
|
||||
...val,
|
||||
owner_id: currentMember.user_id,
|
||||
}
|
||||
})
|
||||
|
||||
setShowTransfer(false)
|
||||
|
||||
setShowAction(false)
|
||||
|
||||
setTimeout(() => {
|
||||
setCurrentMember(null)
|
||||
|
||||
loadData()
|
||||
}, 300)
|
||||
}
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { UserSpace } from '@/types/space'
|
||||
import { router, useLocalSearchParams } from 'expo-router'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Text, TouchableOpacity, View } from 'react-native'
|
||||
import { router, useFocusEffect, useLocalSearchParams } from 'expo-router'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { ActivityIndicator, Text, TouchableOpacity, View } from 'react-native'
|
||||
import { KeyboardAvoidingView } from 'react-native-keyboard-controller'
|
||||
import { toast } from 'sonner-native'
|
||||
import { AButton, AInput, AModal, ASwitch, LucideIcon } from '@/components/common/ao'
|
||||
import EmptyState from '@/components/common/EmptyStatus'
|
||||
import CustomHeader from '@/components/custom/Header'
|
||||
|
||||
import {
|
||||
Actionsheet,
|
||||
ActionsheetBackdrop,
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
ActionsheetDragIndicator,
|
||||
ActionsheetDragIndicatorWrapper,
|
||||
} from '@/components/ui/actionsheet'
|
||||
import { dissolveSpaceApi, exitSpaceApi, getSpaceSettingsApi, updateSpaceDefaultApi, updateSpaceMemberNameApi, updateSpaceNameApi } from '@/request/api/space'
|
||||
import { dissolveSpaceApi, enterSpaceApi, exitSpaceApi, getSpaceSettingsApi, updateSpaceDefaultApi, updateSpaceMemberNameApi, updateSpaceNameApi } from '@/request/api/space'
|
||||
import { useSpaceStore } from '@/store/space'
|
||||
import { useUserStore } from '@/store/user'
|
||||
|
||||
interface ItemProps {
|
||||
@@ -60,6 +61,7 @@ function Item({
|
||||
|
||||
export default function Settings() {
|
||||
const userStore = useUserStore()
|
||||
const spaceStore = useSpaceStore()
|
||||
|
||||
const params = useLocalSearchParams<{ spaceId: string }>()
|
||||
const { spaceId } = params
|
||||
@@ -100,21 +102,19 @@ export default function Settings() {
|
||||
}
|
||||
}, [showName])
|
||||
|
||||
useEffect(() => {
|
||||
if (!spaceId) {
|
||||
toast.warning('空间无效')
|
||||
router.back()
|
||||
}
|
||||
else {
|
||||
getSpaceSettingsApi({
|
||||
space_id: Number(spaceId),
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
setSpace(res.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, [spaceId])
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (spaceId) {
|
||||
getSpaceSettingsApi({
|
||||
space_id: Number(spaceId),
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
setSpace(res.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
}, []),
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (space) {
|
||||
@@ -131,10 +131,41 @@ export default function Settings() {
|
||||
{
|
||||
!space
|
||||
? (
|
||||
<EmptyState title="正在加载空间中 . . ." />
|
||||
<View className="flex-1 items-center justify-center">
|
||||
<ActivityIndicator size="large" color="#333" />
|
||||
</View>
|
||||
)
|
||||
: (
|
||||
<View className="flex-1 px-4 pt-4">
|
||||
<View className="bg-white rounded-xl overflow-hidden mb-6">
|
||||
<Item
|
||||
icon="Earth"
|
||||
title="进入空间"
|
||||
onPress={async () => {
|
||||
const toastId = toast.loading('正在进入中 ...', {
|
||||
duration: Infinity,
|
||||
})
|
||||
|
||||
try {
|
||||
enterSpaceApi({
|
||||
space_id: space.space_id,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
toast.success('进入成功')
|
||||
spaceStore.setCurrentSpace({
|
||||
space_id: space.space_id,
|
||||
role: space.role,
|
||||
})
|
||||
router.navigate('/money')
|
||||
}
|
||||
})
|
||||
}
|
||||
finally {
|
||||
toast.dismiss(toastId)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Text className="text-sm font-semibold text-gray-400 mb-2 tracking-wider">
|
||||
常用设置
|
||||
@@ -175,7 +206,7 @@ export default function Settings() {
|
||||
|
||||
<Item
|
||||
icon="Star"
|
||||
title="设为默认空间"
|
||||
title="默认空间"
|
||||
isLast
|
||||
showRightIcon={false}
|
||||
right={(
|
||||
@@ -229,7 +260,7 @@ export default function Settings() {
|
||||
router.navigate({
|
||||
pathname: '/space/manage/sub-page/invite',
|
||||
params: {
|
||||
invite_code: space.invite_code,
|
||||
space_id: space.space_id,
|
||||
},
|
||||
})
|
||||
}}
|
||||
@@ -245,7 +276,15 @@ export default function Settings() {
|
||||
isOwner && (
|
||||
<Item
|
||||
icon="Users"
|
||||
title="成员管理"
|
||||
title="空间成员"
|
||||
onPress={() => {
|
||||
router.navigate({
|
||||
pathname: '/space/manage/sub-page/member',
|
||||
params: {
|
||||
space_id: space.space_id,
|
||||
},
|
||||
})
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -432,7 +471,7 @@ export default function Settings() {
|
||||
{/* 解散 */}
|
||||
<AModal
|
||||
title="提示"
|
||||
body={isOwner ? '确认解散该空间么?' : '确定解散空间么?'}
|
||||
body={isOwner ? '确认解散该空间么?' : '确定退出空间么?'}
|
||||
isOpen={showDissolve}
|
||||
onClose={() => {
|
||||
setShowDissolve(false)
|
||||
|
||||
Reference in new issue
Block a user