This commit is contained in:
oneao committed 2026-03-01 22:01:04 +08:00
1 parent 10d0cadfe3
commit ab3eaed7ce
11 files changed
+216 -163

No files matched your search

+1
View File
@@ -2,6 +2,7 @@
<project version="4"> <project version="4">
<component name="DataSourcePerFileMappings"> <component name="DataSourcePerFileMappings">
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d/console_1.sql" value="4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d" /> <file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d/console_1.sql" value="4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d" />
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d/console_2.sql" value="4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d" />
<file url="file://$PROJECT_DIR$/db/queries/finance_category_default.sql" value="4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d" /> <file url="file://$PROJECT_DIR$/db/queries/finance_category_default.sql" value="4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d" />
<file url="file://$PROJECT_DIR$/db/queries/finance_category_space.sql" value="4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d" /> <file url="file://$PROJECT_DIR$/db/queries/finance_category_space.sql" value="4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d" />
<file url="file://$PROJECT_DIR$/db/queries/space.sql" value="4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d" /> <file url="file://$PROJECT_DIR$/db/queries/space.sql" value="4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d" />
@@ -3,6 +3,7 @@ import type { NavItem } from '@/store/navigation'
import { Slot, usePathname, useRouter } from 'expo-router' import { Slot, usePathname, useRouter } from 'expo-router'
import { useCallback, useEffect, useState } from 'react' import { useCallback, useEffect, useState } from 'react'
import { Text, TouchableOpacity, View } from 'react-native' import { Text, TouchableOpacity, View } from 'react-native'
import { KeyboardAvoidingView } from 'react-native-keyboard-controller'
import Animated, { useAnimatedRef } from 'react-native-reanimated' import Animated, { useAnimatedRef } from 'react-native-reanimated'
import Sortable from 'react-native-sortables' import Sortable from 'react-native-sortables'
import { ADrawer, LucideIcon } from '@/components/common/ao' import { ADrawer, LucideIcon } from '@/components/common/ao'
@@ -71,7 +72,14 @@ export default function TabsLayout() {
return ( return (
<View className="flex-1 bg-gray-100"> <View className="flex-1 bg-gray-100">
<View className="flex-1 bg-white"> <View className="flex-1 bg-white">
<KeyboardAvoidingView
behavior="padding"
style={{
flex: 1,
}}
>
<Slot /> <Slot />
</KeyboardAvoidingView>
</View> </View>
<View <View
+53 -49
View File
@@ -1,64 +1,68 @@
import { useCallback } from 'react' import React from 'react'
import { StyleSheet, Text, View } from 'react-native' import {
import Sortable from 'react-native-sortables' StyleSheet,
Text,
const DATA = Array.from({ length: 10 }, (_, index) => `Item ${index + 1}`) TextInput,
TouchableOpacity,
export default function CustomHandleExample() { View,
const renderItem = useCallback( } from 'react-native'
({ item }: { item: string }) => ( import { KeyboardAvoidingView } from 'react-native-keyboard-controller'
<View style={styles.card}>
<Text style={styles.text}>{item}</Text>
<Sortable.Handle>
<View style={styles.handle}>
<Text style={styles.handleText}>::</Text>
</View>
</Sortable.Handle>
</View>
),
[],
)
export default function KeyboardAvoidingViewExample() {
return ( return (
<View style={styles.container}> <KeyboardAvoidingView
<Sortable.Grid behavior="padding"
activeItemScale={1.05} keyboardVerticalOffset={100}
columns={1} style={styles.content}
data={DATA} >
overDrag="vertical" <View style={styles.inner}>
renderItem={renderItem} <Text style={styles.heading}>Header</Text>
rowGap={10} <View>
highlight-next-line <TextInput placeholder="Username" style={styles.textInput} />
customHandle // must be set to use a custom handle <TextInput placeholder="Password" style={styles.textInput} />
/>
</View> </View>
<TouchableOpacity style={styles.button}>
<Text style={styles.text}>Submit</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
) )
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
card: { content: {
alignItems: 'center', flex: 1,
backgroundColor: '#2e8555', maxHeight: 600,
borderRadius: 12, },
flexDirection: 'row', heading: {
justifyContent: 'center', fontSize: 36,
marginBottom: 48,
fontWeight: '600',
},
inner: {
padding: 24, padding: 24,
flex: 1,
justifyContent: 'space-between',
}, },
container: { textInput: {
padding: 16, height: 45,
borderColor: '#000000',
borderWidth: 1,
borderRadius: 10,
marginBottom: 36,
paddingLeft: 10,
}, },
handle: { button: {
backgroundColor: 'rgba(0,0,0,0.2)', marginTop: 12,
borderRadius: 5, height: 45,
padding: 10, borderRadius: 10,
}, backgroundColor: 'rgb(40, 64, 147)',
handleText: { justifyContent: 'center',
color: 'white', alignItems: 'center',
fontWeight: 'bold',
}, },
text: { text: {
fontWeight: '500',
fontSize: 16,
color: 'white', color: 'white',
flex: 1,
fontWeight: 'bold',
}, },
}) })
@@ -4,7 +4,6 @@ import { Stack, usePathname } from 'expo-router'
import * as SplashScreen from 'expo-splash-screen' import * as SplashScreen from 'expo-splash-screen'
import { StatusBar } from 'expo-status-bar' import { StatusBar } from 'expo-status-bar'
import { useEffect } from 'react' import { useEffect } from 'react'
import { Modal, View } from 'react-native'
import { GestureHandlerRootView } from 'react-native-gesture-handler' import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { KeyboardProvider } from 'react-native-keyboard-controller' import { KeyboardProvider } from 'react-native-keyboard-controller'
import { MenuProvider } from 'react-native-popup-menu' import { MenuProvider } from 'react-native-popup-menu'
@@ -1,8 +1,10 @@
import type { UserSpace } from '@/types/space' import type { UserSpace } from '@/types/space'
import { useEffect, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { Text, View } from 'react-native' import { Text, View } from 'react-native'
import { KeyboardAvoidingView } from 'react-native-keyboard-controller'
import Animated, { useAnimatedStyle } from 'react-native-reanimated'
import { toast } from 'sonner-native' import { toast } from 'sonner-native'
import { LucideIcon } from '@/components/common/ao' import { AButton, LucideIcon } from '@/components/common/ao'
import { AInput, AModal } from '@/components/common/ao/index' import { AInput, AModal } from '@/components/common/ao/index'
import { import {
Actionsheet, Actionsheet,
@@ -14,7 +16,7 @@ import {
ActionsheetItem, ActionsheetItem,
ActionsheetItemText, ActionsheetItemText,
} from '@/components/ui/actionsheet' } from '@/components/ui/actionsheet'
import { dissolveSpaceApi } from '@/request/api/space' import { dissolveSpaceApi, updateSpaceNameApi } from '@/request/api/space'
import { useUserStore } from '@/store/user' import { useUserStore } from '@/store/user'
interface SettingActionsProps { interface SettingActionsProps {
@@ -31,20 +33,25 @@ export default function SettingActions(
isOpen, isOpen,
onClose, onClose,
space, space,
onActionSuccess onActionSuccess,
}: SettingActionsProps, }: SettingActionsProps,
) { ) {
const userStore = useUserStore() const userStore = useUserStore()
const inputRef = useRef<any>(null)
const [localSpace, setLocalSpace] = useState(space) const [localSpace, setLocalSpace] = useState(space)
const [showNameModal, setShowNameModal] = useState(false) const [showNameModal, setShowNameModal] = useState(false)
const [showMemberNameModal, setShowMemberNameModal] = useState(false) const [showMemberNameModal, setShowMemberNameModal] = useState(false)
useEffect(() => { useEffect(() => {
setLocalSpace(space) setLocalSpace(space)
}, [space]) }, [space])
useEffect(() => {
if (showNameModal) {
inputRef.current?.focus()
}
}, [showNameModal])
return ( return (
<> <>
<Actionsheet <Actionsheet
@@ -112,7 +119,7 @@ export default function SettingActions(
}).then((res) => { }).then((res) => {
if (res.isSuccess) { if (res.isSuccess) {
toast.success('空间已解散') toast.success('空间已解散')
onActionSuccess?. onActionSuccess?.()
} }
}, },
) )
@@ -133,47 +140,36 @@ export default function SettingActions(
</ActionsheetContent> </ActionsheetContent>
</Actionsheet> </Actionsheet>
<Actionsheet
<AModal
title="修改空间名称"
showCloseIcon
body={
(
<View className="gap-2">
<Text>空间名称</Text>
<AInput
value={localSpace?.name || ''}
onChangeText={(val) => {
setLocalSpace(prev => prev
? ({
...prev,
name: val,
})
: null)
}}
className="flex-1"
/>
</View>
)
}
isOpen={showNameModal} isOpen={showNameModal}
onCancel={() => {
setShowNameModal(false)
setLocalSpace(space)
}}
onClose={() => { onClose={() => {
setShowNameModal(false) setShowNameModal(false)
setLocalSpace(space)
}}
onConfirm={async () => {
// 更新名称
if (!localSpace?.name) {
toast.warning('请输入空间名称')
}
}} }}
> >
</AModal> <KeyboardAvoidingView
behavior="position"
>
<ActionsheetBackdrop />
<ActionsheetContent>
<ActionsheetDragIndicatorWrapper>
<ActionsheetDragIndicator />
</ActionsheetDragIndicatorWrapper>
<View className="pb-6">
<View className="items-center mb-6">
<Text className="text-lg font-semibold">空间名称</Text>
</View>
<View className="w-full mb-4">
<AInput ref={inputRef} className="w-full" placeholder="请输入空间名称" />
</View>
<AButton className="rounded-full">保存</AButton>
</View>
</ActionsheetContent>
</KeyboardAvoidingView>
</Actionsheet>
</> </>
) )
} }
@@ -39,6 +39,7 @@ export default function AuthSpace() {
function listUserSpaces() { function listUserSpaces() {
listUserSpacesApi().then((res) => { listUserSpacesApi().then((res) => {
if (res.isSuccess) { if (res.isSuccess) {
console.log(res.data)
setSpaces(res.data.spaces) setSpaces(res.data.spaces)
} }
}) })
@@ -55,8 +56,8 @@ export default function AuthSpace() {
style={{ style={{
height: ITEM_HEIGHT, height: ITEM_HEIGHT,
}} }}
className={`rounded-md flex-row items-center justify-between px-4 py-3 border className={`bg-[#f9f9f9] rounded-md flex-row items-center justify-between px-4 py-3 border
${item.space_id === selectedId ? 'border-gray-700' : 'border-gray-300'}`} ${item.space_id === selectedId ? 'border-gray-700' : 'border-gray-200'}`}
> >
<View className="flex-row items-center"> <View className="flex-row items-center">
<Sortable.Handle> <Sortable.Handle>
@@ -211,6 +212,9 @@ export default function AuthSpace() {
: ( : (
<Sortable.Grid <Sortable.Grid
scrollableRef={scrollableRef} scrollableRef={scrollableRef}
keyExtractor={(item: UserSpace) => {
return String(item.space_id)
}}
activeItemScale={1} activeItemScale={1}
columns={1} columns={1}
data={spaces} data={spaces}
@@ -0,0 +1,13 @@
import { View } from 'react-native'
import CustomHeader from '@/components/custom/Header'
export default function Settings() {
return (
<View className="flex-1">
<CustomHeader title="空间设置" />
<View></View>
</View>
)
}
@@ -14,20 +14,27 @@ import type {
RemoveSpaceMemberResult, RemoveSpaceMemberResult,
UpdateSpaceMemberRoleParams, UpdateSpaceMemberRoleParams,
UpdateSpaceMemberRoleResult, UpdateSpaceMemberRoleResult,
UpdateSpaceNameParams,
} from '@/types/space' } from '@/types/space'
import alovaInstance from '../../index' import alovaInstance from '../../index'
const API_PREFIX = '/space' const API_PREFIX = '/space'
// Create Space API // 创建空间
export function createSpaceApi(params: CreateSpaceParams) { export function createSpaceApi(params: CreateSpaceParams) {
return alovaInstance.Post<ResponseData<CreateSpaceResult>>(`${API_PREFIX}/create`, params) return alovaInstance.Post<ResponseData<CreateSpaceResult>>(`${API_PREFIX}/create`, params)
} }
// 查询用户下的空间列表
export function listUserSpacesApi() { export function listUserSpacesApi() {
return alovaInstance.Post<ResponseData<ListUserSpacesResult>>(`${API_PREFIX}/list`) return alovaInstance.Post<ResponseData<ListUserSpacesResult>>(`${API_PREFIX}/list`)
} }
// 修改空间名称
export function updateSpaceNameApi(params: UpdateSpaceNameParams) {
return alovaInstance.Post<ResponseData<void>>(`${API_PREFIX}/name`, params)
}
// Join Space API // Join Space API
export function joinSpaceApi(params: JoinSpaceParams) { export function joinSpaceApi(params: JoinSpaceParams) {
return alovaInstance.Post<ResponseData<JoinSpaceResult>>(`${API_PREFIX}/join`, params) return alovaInstance.Post<ResponseData<JoinSpaceResult>>(`${API_PREFIX}/join`, params)
+2 -2
View File
@@ -28,8 +28,8 @@ function isWhiteListed(url: string) {
} }
const alovaInstance = createAlova({ const alovaInstance = createAlova({
// baseURL: 'http://192.168.5.6:8086/app', baseURL: 'http://192.168.5.6:8086/app',
baseURL: 'http://192.168.1.137:8086/app', // baseURL: 'http://192.168.1.137:8086/app',
requestAdapter: axiosRequestAdapter(), requestAdapter: axiosRequestAdapter(),
timeout: 5000, timeout: 5000,
cacheFor: null, cacheFor: null,
@@ -34,6 +34,12 @@ export interface DissolveSpaceParams {
export interface DissolveSpaceResult { export interface DissolveSpaceResult {
} }
// 更新名称
export interface UpdateSpaceNameParams {
space_id: number
space_name: string
}
// 加入空间 // 加入空间
export interface JoinSpaceParams { export interface JoinSpaceParams {
invite_code: string invite_code: string
+80 -65
View File
@@ -1,67 +1,82 @@
import { useCallback } from 'react'; <!DOCTYPE html>
import { StyleSheet, Text, View } from 'react-native'; <html>
import Sortable from 'react-native-sortables'; <head>
<meta charset="UTF-8"/>
const DATA = Array.from({ length: 4 }, (_, index) => `Item ${index + 1}`); <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>空间状态演示</title>
export default function CustomHandleExample() { <style>
const renderItem = useCallback( body{margin:0;font-family:-apple-system;background:#fff;padding:20px;}
({ item }: { item: string }) => ( .space{
<View style={styles.card}> background:#f8f8f8;
<Text style={styles.text}>{item}</Text> border-radius:12px;
{/* Wraps the handle component */} padding:16px;
{/* highlight-next-line */} margin-bottom:12px;
<Sortable.Handle> display:flex;
<View style={styles.handle}> justify-content:space-between;
<Text style={styles.handleText}>::</Text> align-items:center;
</View> border:2px solid transparent;
{/* highlight-next-line */}
</Sortable.Handle>
</View>
),
[]
);
return (
<View style={styles.container}>
<Sortable.Grid
activeItemScale={1.05}
columns={1}
data={DATA}
overDrag='vertical'
renderItem={renderItem}
rowGap={10}
// highlight-next-line
customHandle // must be set to use a custom handle
/>
</View>
);
} }
.space.selected{
border:2px solid #111;
}
.name{
font-weight:600;
}
.meta{font-size:12px;color:#666;margin-top:4px;}
.tag{
font-size:10px;
padding:2px 6px;
border-radius:6px;
margin-left:6px;
}
.tag-default{
background:#e5e7eb;
color:#555;
}
.tag-current{
background:#111;
color:#fff;
}
.more{font-size:18px;color:#999;}
</style>
</head>
<body>
const styles = StyleSheet.create({ <!-- 当前正在使用 -->
card: { <div class="space">
alignItems: 'center', <div>
backgroundColor: '#2e8555', <div class="name">
borderRadius: 12, 工作空间
flexDirection: 'row', <span class="tag tag-current">当前</span>
justifyContent: 'center', <span class="tag tag-default">默认</span>
padding: 24 </div>
}, <div class="meta">张三 · 5人</div>
container: { </div>
padding: 16 <div class="more">⋯</div>
}, </div>
handle: {
backgroundColor: 'rgba(0,0,0,0.2)', <!-- 只是默认 -->
borderRadius: 5, <div class="space">
padding: 10 <div>
}, <div class="name">
handleText: { 家庭账本
color: 'white', <span class="tag tag-default">默认</span>
fontWeight: 'bold' </div>
}, <div class="meta">张三 · 3人</div>
text: { </div>
color: 'white', <div class="more">⋯</div>
flex: 1, </div>
fontWeight: 'bold'
} <!-- 被选中但未进入 -->
}); <div class="space selected">
<div>
<div class="name">
副业空间
</div>
<div class="meta">张三 · 2人</div>
</div>
<div class="more">⋯</div>
</div>
</body>
</html>