u
This commit is contained in:
1 parent
4dcac7b5a0
commit
59d3be2299
25 files changed
+380
-125
No files matched your search
@@ -7,6 +7,7 @@ import { IconQq, IconWeixin, IconZhiwen } from '@/components/iconfont'
|
||||
import { Divider } from '@/components/ui/divider'
|
||||
import useFormValidation from '@/hooks/useFormValidation'
|
||||
import { loginDefaultApi } from '@/request/api/auth'
|
||||
import { useMoneyCategoryStore } from '@/store/moneyCategory'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { toast } from '@/utils/toast'
|
||||
|
||||
@@ -20,6 +21,7 @@ interface LoginForm {
|
||||
export default function AuthLogin() {
|
||||
const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
const moneyCategoryStore = useMoneyCategoryStore()
|
||||
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [form, setForm] = useState<LoginForm>({
|
||||
@@ -60,6 +62,7 @@ export default function AuthLogin() {
|
||||
refreshToken: res.data.refreshToken,
|
||||
rememberMe: form.rememberMe,
|
||||
})
|
||||
moneyCategoryStore.init()
|
||||
|
||||
router.replace('/user')
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@ import { Link, LinkText } from '@/components/ui/link'
|
||||
|
||||
interface JoinSpaceProps {
|
||||
onBack?: () => void
|
||||
onJoin?: (code: string) => void
|
||||
}
|
||||
|
||||
export default function JoinSpace({
|
||||
onBack,
|
||||
onJoin,
|
||||
}: JoinSpaceProps) {
|
||||
const formRef = useRef<AFormRef>(null)
|
||||
const [form, setForm] = useState({
|
||||
@@ -22,11 +24,32 @@ export default function JoinSpace({
|
||||
name="username"
|
||||
label="邀请码"
|
||||
>
|
||||
<AInput placeholder="请输入邀请码"></AInput>
|
||||
<AInput
|
||||
placeholder="请输入邀请码"
|
||||
value={form.code}
|
||||
onChangeText={text => setForm({
|
||||
...form,
|
||||
code: text,
|
||||
})}
|
||||
>
|
||||
</AInput>
|
||||
</AFormItem>
|
||||
</AForm>
|
||||
|
||||
<AButton className="w-full">加入空间</AButton>
|
||||
<AButton
|
||||
className="w-full"
|
||||
onPress={() => {
|
||||
if (form.code === '') {
|
||||
//
|
||||
}
|
||||
else {
|
||||
onJoin?.(form.code)
|
||||
}
|
||||
}}
|
||||
>
|
||||
加入空间
|
||||
</AButton>
|
||||
|
||||
<View
|
||||
className="flex-row items-center w-full"
|
||||
style={{
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { UserSpace } from '@/types/space/spaceUser'
|
||||
import { Text, View } from 'react-native'
|
||||
import { AButton, ADivider, LucideIcon } from '@/components/common/ao'
|
||||
|
||||
|
||||
+46
-20
@@ -1,9 +1,10 @@
|
||||
import type { SpaceUser, UserSpace } from '@/types/space/spaceUser'
|
||||
import * as Clipboard from 'expo-clipboard'
|
||||
import { useState } from 'react'
|
||||
import { FlatList, Pressable, TouchableOpacity, View } from 'react-native'
|
||||
import QRCode from 'react-native-qrcode-svg'
|
||||
import { AButton, ADivider, LucideIcon } from '@/components/common/ao'
|
||||
|
||||
import { AButton, ADivider, LucideIcon } from '@/components/common/ao'
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
@@ -25,16 +26,22 @@ import { ChevronDownIcon, ChevronUpIcon } from '@/components/ui/icon'
|
||||
import { Menu, MenuItem, MenuItemLabel } from '@/components/ui/menu'
|
||||
import { Text } from '@/components/ui/text'
|
||||
import { VStack } from '@/components/ui/vstack'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { formatRelativeTime } from '@/utils/time'
|
||||
import { toast } from '@/utils/toast'
|
||||
|
||||
export default function SpaceInviteAndMembers() {
|
||||
const [accordion, setAccordion] = useState(['a'])
|
||||
interface SpaceInviteProps {
|
||||
userSpace: UserSpace | null
|
||||
spaceUsers: SpaceUser[]
|
||||
}
|
||||
|
||||
const members = [
|
||||
{ id: 1, name: '张三' },
|
||||
{ id: 2, name: '李四' },
|
||||
{ id: 3, name: '王五' },
|
||||
]
|
||||
export default function SpaceInvite({
|
||||
userSpace,
|
||||
spaceUsers,
|
||||
}: SpaceInviteProps) {
|
||||
const userStore = useUserStore()
|
||||
|
||||
const [accordion, setAccordion] = useState(['a'])
|
||||
|
||||
async function copyInviteCode() {
|
||||
await Clipboard.setStringAsync('hello world')
|
||||
@@ -94,7 +101,7 @@ export default function SpaceInviteAndMembers() {
|
||||
copyInviteCode()
|
||||
}}
|
||||
>
|
||||
<Text className="font-bold text-3xl tracking-[4px] mr-3">123456</Text>
|
||||
<Text className="font-bold text-3xl tracking-[4px] mr-3">{userSpace?.invite_code}</Text>
|
||||
<LucideIcon icon="Copy" size={16} color="blue" clickable={false} />
|
||||
</TouchableOpacity>
|
||||
|
||||
@@ -102,7 +109,7 @@ export default function SpaceInviteAndMembers() {
|
||||
<ADivider direction="horizontal" text="扫描二维码" />
|
||||
<QRCode
|
||||
size={120}
|
||||
value="http://awesome.link.qr"
|
||||
value={userSpace?.invite_code}
|
||||
/>
|
||||
</View>
|
||||
</AccordionContent>
|
||||
@@ -112,31 +119,50 @@ export default function SpaceInviteAndMembers() {
|
||||
<View className="flex flex-row items-center justify-between">
|
||||
<Text className="font-semibold text-lg">空间成员</Text>
|
||||
<Badge size="lg" variant="solid" action="muted">
|
||||
<BadgeText>3 人</BadgeText>
|
||||
<BadgeText>
|
||||
{spaceUsers.length}
|
||||
{' '}
|
||||
人
|
||||
</BadgeText>
|
||||
</Badge>
|
||||
</View>
|
||||
|
||||
<View>
|
||||
<FlatList
|
||||
data={members}
|
||||
keyExtractor={item => item.id.toString()}
|
||||
data={spaceUsers}
|
||||
keyExtractor={item => item.user_id.toString()}
|
||||
renderItem={({ item }) => (
|
||||
<TouchableOpacity>
|
||||
<HStack space="md" className="my-2 items-center">
|
||||
<Avatar className="bg-indigo-600">
|
||||
<AvatarFallbackText className="text-white text-lg">
|
||||
{item.name}
|
||||
<AvatarFallbackText className="text-white text-2xl">
|
||||
{item.username}
|
||||
</AvatarFallbackText>
|
||||
<AvatarBadge />
|
||||
</Avatar>
|
||||
<VStack>
|
||||
<HStack space="sm">
|
||||
<Heading size="sm">{item.name}</Heading>
|
||||
<Badge size="sm" variant="solid" action="muted">
|
||||
<BadgeText>管理员</BadgeText>
|
||||
</Badge>
|
||||
<Heading size="sm">{item.username}</Heading>
|
||||
{[0, 1].includes(item.space_role) && (
|
||||
<Badge size="sm" variant="solid" action="muted">
|
||||
<BadgeText>
|
||||
{item.space_role === 0 ? '创建者' : '管理员'}
|
||||
</BadgeText>
|
||||
</Badge>
|
||||
)}
|
||||
|
||||
{
|
||||
(userStore.userInfo?.id === item.user_id) && (
|
||||
<Badge size="sm" variant="solid" action='info'>
|
||||
<BadgeText>
|
||||
我
|
||||
</BadgeText>
|
||||
</Badge>
|
||||
)
|
||||
}
|
||||
|
||||
</HStack>
|
||||
<Text size="sm">Marketing Coordinator</Text>
|
||||
<Text size="sm" className="mt-1">{formatRelativeTime(item.active_time)}</Text>
|
||||
</VStack>
|
||||
|
||||
<View className="ml-auto">
|
||||
@@ -1,18 +1,87 @@
|
||||
import type { SpaceUser, UserSpace } from '@/types/space/spaceUser'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { View } from 'react-native'
|
||||
import CustomHeader from '@/components/custom/Header'
|
||||
|
||||
import { createSpaceApi } from '@/request/api/space/space'
|
||||
import { getUserSpaceApi, joinSpaceApi, listSpaceUsersApi } from '@/request/api/space/spaceUser'
|
||||
import JoinSpace from './components/JoinSpace'
|
||||
import SpaceHome from './components/SpaceHome'
|
||||
import SpaceInviteAndMembers from './components/SpaceInviteAndMembers'
|
||||
|
||||
import SpaceInviteAndMembers from './components/SpaceInvite'
|
||||
import StepFade from './components/StepFade'
|
||||
|
||||
export default function PagerWithTab() {
|
||||
const [step, setStep] = useState<'join' | 'invite' | 'home'>('home')
|
||||
const [userSpace, setUserSpace] = useState<UserSpace | null>(null)
|
||||
const [spaceUsers, setSpaceUsers] = useState<SpaceUser[]>([])
|
||||
|
||||
const fetchData = async () => {
|
||||
await Promise.all([
|
||||
getUserSpaceApi({})
|
||||
.then((res) => {
|
||||
if (res.isSuccess) {
|
||||
if (res.data === null) {
|
||||
setUserSpace(null)
|
||||
setStep('home')
|
||||
}
|
||||
else {
|
||||
setUserSpace({
|
||||
space_id: res.data.space_id,
|
||||
space_name: res.data.space_name,
|
||||
role: res.data.role,
|
||||
invite_code: res.data.invite_code,
|
||||
})
|
||||
setStep('invite')
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 提示
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('getUserSpaceApi error:', err)),
|
||||
|
||||
listSpaceUsersApi({})
|
||||
.then((res) => {
|
||||
if (res.isSuccess) {
|
||||
setSpaceUsers(res.data.users)
|
||||
}
|
||||
else {
|
||||
// 提示
|
||||
}
|
||||
})
|
||||
.catch(err => console.error('listSpaceUsersApi error:', err)),
|
||||
])
|
||||
}
|
||||
useEffect(() => {
|
||||
fetchData()
|
||||
}, [])
|
||||
|
||||
function createSpace() {
|
||||
createSpaceApi({}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
setUserSpace({
|
||||
space_id: res.data.id,
|
||||
space_name: '',
|
||||
role: 0,
|
||||
invite_code: res.data.invite_code,
|
||||
})
|
||||
|
||||
setStep('invite')
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
function joinSpace(code: string) {
|
||||
joinSpaceApi({
|
||||
invite_code: code,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
fetchData()
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -21,9 +90,7 @@ export default function PagerWithTab() {
|
||||
<View className="flex-1">
|
||||
<StepFade visible={step === 'home'}>
|
||||
<SpaceHome
|
||||
onCreate={() => {
|
||||
setStep('invite')
|
||||
}}
|
||||
onCreate={createSpace}
|
||||
onJoin={() => {
|
||||
setStep('join')
|
||||
}}
|
||||
@@ -35,11 +102,16 @@ export default function PagerWithTab() {
|
||||
onBack={() => {
|
||||
setStep('home')
|
||||
}}
|
||||
onJoin={(code) => {
|
||||
if (code !== '') {
|
||||
joinSpace(code)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</StepFade>
|
||||
|
||||
<StepFade visible={step === 'invite'}>
|
||||
<SpaceInviteAndMembers />
|
||||
<SpaceInviteAndMembers userSpace={userSpace} spaceUsers={spaceUsers} />
|
||||
</StepFade>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function CustomHeader(props: CustomHeaderProps) {
|
||||
{...screenProps}
|
||||
options={{
|
||||
...screenProps?.options,
|
||||
headerShown: true,
|
||||
headerShown: false,
|
||||
header: () => (
|
||||
<SafeAreaView
|
||||
className={`${className}`}
|
||||
|
||||
@@ -31,8 +31,8 @@ function isWhiteListed(url: string) {
|
||||
}
|
||||
|
||||
const alovaInstance = createAlova({
|
||||
baseURL: 'http://192.168.5.6:8086/app',
|
||||
// baseURL: 'http://192.168.1.136:8086/app',
|
||||
// baseURL: 'http://192.168.5.6:8086/app',
|
||||
baseURL: 'http://192.168.1.136:8086/app',
|
||||
requestAdapter: axiosRequestAdapter(),
|
||||
timeout: 5000,
|
||||
cacheFor: null,
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
export interface UserSpace {
|
||||
space_id: number
|
||||
space_name: string
|
||||
role: number
|
||||
invite_code: string
|
||||
}
|
||||
|
||||
// 获取用户的空间
|
||||
export interface GetUserSpaceParams {
|
||||
|
||||
@@ -22,6 +29,7 @@ export interface SpaceUser {
|
||||
user_id: number
|
||||
username: string
|
||||
space_role: number
|
||||
active_time: string
|
||||
}
|
||||
|
||||
export interface ListSpaceUsersParams {
|
||||
|
||||
@@ -68,3 +68,40 @@ export function isInMonth(date: string, month: string): boolean {
|
||||
// month 形如 "2025-01"
|
||||
return dayjs(date).format('YYYY-MM') === month
|
||||
}
|
||||
|
||||
export function formatRelativeTime(date: string | Date | number): string {
|
||||
if (typeof date === 'string' && !date.trim()) {
|
||||
return '暂无活跃'
|
||||
}
|
||||
|
||||
const d = dayjs(date)
|
||||
if (!d.isValid())
|
||||
return '暂无活跃'
|
||||
|
||||
const now = dayjs()
|
||||
const diff = now.diff(d, 'second') // 相差秒数
|
||||
|
||||
if (diff < 60) {
|
||||
return '刚刚'
|
||||
}
|
||||
else if (diff < 3600) {
|
||||
const minutes = Math.floor(diff / 60)
|
||||
return `${minutes}分钟前`
|
||||
}
|
||||
else if (diff < 86400) {
|
||||
const hours = Math.floor(diff / 3600)
|
||||
return `${hours}小时前`
|
||||
}
|
||||
else if (diff < 2592000) { // 30天以内
|
||||
const days = Math.floor(diff / 86400)
|
||||
return `${days}天前`
|
||||
}
|
||||
else if (diff < 31536000) { // 1年以内
|
||||
const months = Math.floor(diff / 2592000)
|
||||
return `${months}个月前`
|
||||
}
|
||||
else {
|
||||
const years = Math.floor(diff / 31536000)
|
||||
return `${years}年前`
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user