This commit is contained in:
oneao committed 2026-02-28 17:02:35 +08:00
1 parent fc005e0fb4
commit 10d0cadfe3
18 files changed
+591 -133

No files matched your search

+1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<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://$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/space.sql" value="4db2dbd4-857e-4234-ba5e-f8cbf0ba6e5d" />
@@ -86,7 +86,7 @@ func CreateSpace(c fiber.Ctx) error {
UserID: userID,
Role: 0,
IsDefault: int16(isDefault),
UserName: dto.UserName,
MemberName: dto.MemberName,
SortNumber: dto.SortNumber,
}
@@ -114,6 +114,7 @@ func CreateSpace(c fiber.Ctx) error {
return responsex.OK(c, vo)
}
// ListUserSpaces 查询该用户的空间
func ListUserSpaces(c fiber.Ctx) error {
vo := new(types.ListSpacesVO)
@@ -133,13 +134,13 @@ func ListUserSpaces(c fiber.Ctx) error {
for _, row := range rows {
userSpaces = append(userSpaces, types.UserSpace{
Id: row.ID,
SpaceId: row.SpaceID,
OwnerId: row.OwnerID,
Name: row.Name,
Role: row.Role,
MemberCount: row.MemberCount,
IsDefault: row.IsDefault,
UserName: row.UserName,
MemberName: row.MemberName,
SortNumber: row.SortNumber,
})
}
@@ -149,6 +150,127 @@ func ListUserSpaces(c fiber.Ctx) error {
return responsex.OK(c, vo)
}
// DissolveSpace 解散空间
func DissolveSpace(c fiber.Ctx) error {
dto := new(types.DissolveSpaceDTO)
if err := requestx.BindAndValidate(c, dto); err != nil {
return err
}
userId, err := middleware.GetUserID(c)
if err != nil {
return err
}
spaceId := dto.SpaceId
space, err := db.Queries.GetSpaceById(c.Context(), spaceId)
if err != nil {
if errorx.IsNotFound(err) {
return responsex.Error(c, "空间不存在或已解散")
}
return err
}
if userId != space.OwnerID {
return responsex.Error(c, "您不是该空间的拥有者,无法解散")
}
if err := db.Queries.DissolveSpace(c.Context(), spaceId); err != nil {
return err
}
return responsex.OK(c, "空间已成功解散")
}
// UpdateMemberName 更新群成员名称
func UpdateMemberName(c fiber.Ctx) error {
dto := new(types.UpdateMemberNameDTO)
if err := requestx.BindAndValidate(c, dto); err != nil {
return err
}
userId, err := middleware.GetUserID(c)
if err != nil {
return err
}
err = db.Queries.UpdateSpaceMemberName(c.Context(), repo.UpdateSpaceMemberNameParams{
SpaceID: dto.SpaceId,
UserID: userId,
MemberName: dto.MemberName,
})
if err != nil {
return err
}
return responsex.OK(c, "更新成功")
}
// UpdateSpaceDefault 更新空间默认情况
func UpdateSpaceDefault(c fiber.Ctx) error {
dto := new(types.UpdateSpaceIsDefaultDTO)
if err := requestx.BindAndValidate(c, dto); err != nil {
return err
}
userId, err := middleware.GetUserID(c)
if err != nil {
return err
}
err = db.Queries.UpdateSpaceIsDefault(c.Context(), repo.UpdateSpaceIsDefaultParams{
SpaceID: dto.SpaceId,
UserID: userId,
IsDefault: dto.IsDefault,
})
if err != nil {
return err
}
return responsex.OK(c, "更新成功")
}
func UpdateSpaceName(c fiber.Ctx) error {
dto := new(types.UpdateSpaceNameDTO)
if err := requestx.BindAndValidate(c, dto); err != nil {
return err
}
userId, err := middleware.GetUserID(c)
if err != nil {
return err
}
spaceId := dto.SpaceId
space, err := db.Queries.GetSpaceById(c.Context(), spaceId)
if err != nil {
if errorx.IsNotFound(err) {
return responsex.Error(c, "空间不存在或已解散")
}
return err
}
if userId != space.OwnerID {
return responsex.Error(c, "您不是该空间的拥有者,无法修改群名称")
}
err = db.Queries.UpdateSpaceName(c.Context(), repo.UpdateSpaceNameParams{
Name: dto.SpaceName,
ID: spaceId,
})
if err != nil {
return err
}
return responsex.OK(c, "更新成功")
}
// 初始化记账分类
func initFinanceCategory(ctx context.Context, q *repo.Queries, spaceId int64, userId int64) error {
categoryDefaults, err := q.ListFinanceCategoryDefault(ctx)
if err != nil {
@@ -23,6 +23,11 @@ func SetupRouters(app *fiber.App) {
spaceRouter.Post("/create", space.CreateSpace)
spaceRouter.Post("/list", space.ListUserSpaces)
spaceRouter.Post("/dissolve", space.DissolveSpace)
spaceRouter.Post("/name", space.UpdateSpaceName)
spaceRouter.Post("/member/default", space.UpdateSpaceDefault)
spaceRouter.Post("/member/name", space.UpdateMemberName)
// authGroup.Get("/user/profile", user.GetProfile)
// spaceGroup.Get("/space/info", space.GetInfo)
+22 -3
View File
@@ -2,7 +2,7 @@ package types
type CreateSpaceDTO struct {
Name string `json:"name" validate:"required"`
UserName string `json:"user_name" validate:"required"`
MemberName string `json:"member_name" validate:"required"`
SortNumber int32 `json:"sort_number" validate:"required"`
}
@@ -13,16 +13,35 @@ type CreateSpaceVO struct {
}
type UserSpace struct {
Id int64 `json:"id"`
SpaceId int64 `json:"space_id"`
OwnerId int64 `json:"owner_id"`
Name string `json:"name"`
Role int16 `json:"role"`
MemberCount int64 `json:"member_count"`
IsDefault int16 `json:"is_default"`
UserName string `json:"user_name"`
MemberName string `json:"member_name"`
SortNumber int32 `json:"sort_number"`
}
type ListSpacesVO struct {
Spaces []UserSpace `json:"spaces"`
}
type DissolveSpaceDTO struct {
SpaceId int64 `json:"space_id" validate:"required"`
}
type UpdateMemberNameDTO struct {
SpaceId int64 `json:"space_id" validate:"required"`
MemberName string `json:"member_name" validate:"required"`
}
type UpdateSpaceIsDefaultDTO struct {
IsDefault int16 `json:"is_default" validate:"required"`
SpaceId int64 `json:"space_id" validate:"required"`
}
type UpdateSpaceNameDTO struct {
SpaceId int64 `json:"space_id" validate:"required"`
SpaceName string `json:"space_name" validate:"required"`
}
+13 -6
View File
@@ -9,9 +9,12 @@ INSERT INTO space (
id,name,invite_code,owner_id,create_time,update_time
) VALUES ($1,$2,$3,$4,NOW(),NOW());
-- 删除空间
-- name: DeleteSpace :exec
DELETE FROM space WHERE id = $1;
-- name: DissolveSpace :exec
UPDATE space SET
status = 2,
dissolve_time = NOW(),
update_time = NOW()
WHERE id = $1;
-- 根据邀请码获取
-- name: GetSpaceByInviteCode :one
@@ -19,13 +22,13 @@ SELECT * FROM space WHERE invite_code = $1;
-- name: ListSpacesByUserId :many
SELECT
s.id,
s.id AS space_id,
s.owner_id,
s.name,
sm.role,
COALESCE(mc.member_count, 0) AS member_count,
sm.is_default,
sm.user_name,
sm.member_name,
sm.sort_number
FROM space s
JOIN space_member sm
@@ -37,4 +40,8 @@ FROM space s
) mc
ON s.id = mc.space_id
WHERE sm.user_id = $1
ORDER BY sm.is_default DESC, sm.sort_number ASC;
ORDER BY sm.is_default DESC, sm.sort_number ASC;
-- name: UpdateSpaceName :exec
UPDATE space SET name = $1
WHERE id = $2;
@@ -5,11 +5,23 @@ FROM space_member
WHERE user_id = $1
AND space_id = $2;
-- 插入空间用户
-- name: InsertSpaceMember :exec
INSERT INTO space_member(id,space_id,user_id,role,is_default,user_name,sort_number,join_time,update_time)
INSERT INTO space_member(id,space_id,user_id,role,is_default,member_name,sort_number,join_time,update_time)
VALUES ($1,$2,$3,$4,$5,$6,$7,NOW(),NOW());
-- 合计某个用户的空间数量
-- name: CountUserSpaces :one
SELECT COUNT(1)
FROM space_member
WHERE user_id = $1;
WHERE user_id = $1;
-- 更新用户在空间内的名称
-- name: UpdateSpaceMemberName :exec
UPDATE space_member SET member_name = $1
WHERE space_id = $2 AND user_id = $3;
-- 更新空间是否为默认
-- name: UpdateSpaceIsDefault :exec
UPDATE space_member SET is_default = $1
WHERE space_id = $2 AND user_id = $3;
+21 -15
View File
@@ -32,24 +32,30 @@ type FinanceCategorySpace struct {
}
type Space struct {
ID int64
Name string
InviteCode string
OwnerID int64
CreateTime pgtype.Timestamptz
UpdateTime pgtype.Timestamptz
ID int64
Name string
InviteCode string
OwnerID int64
Status int16
DisabledTime pgtype.Timestamptz
DissolveTime pgtype.Timestamptz
CreateTime pgtype.Timestamptz
UpdateTime pgtype.Timestamptz
}
type SpaceMember struct {
ID int64
SpaceID int64
UserID int64
Role int16
IsDefault int16
UserName string
SortNumber int32
JoinTime pgtype.Timestamptz
UpdateTime pgtype.Timestamptz
ID int64
SpaceID int64
UserID int64
Role int16
IsDefault int16
SortNumber int32
MemberName string
Status int16
DisabledTime pgtype.Timestamptz
ExitTime pgtype.Timestamptz
JoinTime pgtype.Timestamptz
UpdateTime pgtype.Timestamptz
}
type User struct {
+37 -13
View File
@@ -9,18 +9,21 @@ import (
"context"
)
const deleteSpace = `-- name: DeleteSpace :exec
DELETE FROM space WHERE id = $1
const dissolveSpace = `-- name: DissolveSpace :exec
UPDATE space SET
status = 2,
dissolve_time = NOW(),
update_time = NOW()
WHERE id = $1
`
// 删除空间
func (q *Queries) DeleteSpace(ctx context.Context, id int64) error {
_, err := q.db.Exec(ctx, deleteSpace, id)
func (q *Queries) DissolveSpace(ctx context.Context, id int64) error {
_, err := q.db.Exec(ctx, dissolveSpace, id)
return err
}
const getSpaceById = `-- name: GetSpaceById :one
SELECT id, name, invite_code, owner_id, create_time, update_time FROM space WHERE id = $1
SELECT id, name, invite_code, owner_id, status, disabled_time, dissolve_time, create_time, update_time FROM space WHERE id = $1
`
// 根据id获取分享空间信息
@@ -32,6 +35,9 @@ func (q *Queries) GetSpaceById(ctx context.Context, id int64) (Space, error) {
&i.Name,
&i.InviteCode,
&i.OwnerID,
&i.Status,
&i.DisabledTime,
&i.DissolveTime,
&i.CreateTime,
&i.UpdateTime,
)
@@ -39,7 +45,7 @@ func (q *Queries) GetSpaceById(ctx context.Context, id int64) (Space, error) {
}
const getSpaceByInviteCode = `-- name: GetSpaceByInviteCode :one
SELECT id, name, invite_code, owner_id, create_time, update_time FROM space WHERE invite_code = $1
SELECT id, name, invite_code, owner_id, status, disabled_time, dissolve_time, create_time, update_time FROM space WHERE invite_code = $1
`
// 根据邀请码获取
@@ -51,6 +57,9 @@ func (q *Queries) GetSpaceByInviteCode(ctx context.Context, inviteCode string) (
&i.Name,
&i.InviteCode,
&i.OwnerID,
&i.Status,
&i.DisabledTime,
&i.DissolveTime,
&i.CreateTime,
&i.UpdateTime,
)
@@ -83,13 +92,13 @@ func (q *Queries) InsertSpace(ctx context.Context, arg InsertSpaceParams) error
const listSpacesByUserId = `-- name: ListSpacesByUserId :many
SELECT
s.id,
s.id AS space_id,
s.owner_id,
s.name,
sm.role,
COALESCE(mc.member_count, 0) AS member_count,
sm.is_default,
sm.user_name,
sm.member_name,
sm.sort_number
FROM space s
JOIN space_member sm
@@ -105,13 +114,13 @@ ORDER BY sm.is_default DESC, sm.sort_number ASC
`
type ListSpacesByUserIdRow struct {
ID int64
SpaceID int64
OwnerID int64
Name string
Role int16
MemberCount int64
IsDefault int16
UserName string
MemberName string
SortNumber int32
}
@@ -125,13 +134,13 @@ func (q *Queries) ListSpacesByUserId(ctx context.Context, userID int64) ([]ListS
for rows.Next() {
var i ListSpacesByUserIdRow
if err := rows.Scan(
&i.ID,
&i.SpaceID,
&i.OwnerID,
&i.Name,
&i.Role,
&i.MemberCount,
&i.IsDefault,
&i.UserName,
&i.MemberName,
&i.SortNumber,
); err != nil {
return nil, err
@@ -143,3 +152,18 @@ func (q *Queries) ListSpacesByUserId(ctx context.Context, userID int64) ([]ListS
}
return items, nil
}
const updateSpaceName = `-- name: UpdateSpaceName :exec
UPDATE space SET name = $1
WHERE id = $2
`
type UpdateSpaceNameParams struct {
Name string
ID int64
}
func (q *Queries) UpdateSpaceName(ctx context.Context, arg UpdateSpaceNameParams) error {
_, err := q.db.Exec(ctx, updateSpaceName, arg.Name, arg.ID)
return err
}
@@ -43,7 +43,7 @@ func (q *Queries) GetUserSpaceRole(ctx context.Context, arg GetUserSpaceRolePara
}
const insertSpaceMember = `-- name: InsertSpaceMember :exec
INSERT INTO space_member(id,space_id,user_id,role,is_default,user_name,sort_number,join_time,update_time)
INSERT INTO space_member(id,space_id,user_id,role,is_default,member_name,sort_number,join_time,update_time)
VALUES ($1,$2,$3,$4,$5,$6,$7,NOW(),NOW())
`
@@ -53,7 +53,7 @@ type InsertSpaceMemberParams struct {
UserID int64
Role int16
IsDefault int16
UserName string
MemberName string
SortNumber int32
}
@@ -64,8 +64,40 @@ func (q *Queries) InsertSpaceMember(ctx context.Context, arg InsertSpaceMemberPa
arg.UserID,
arg.Role,
arg.IsDefault,
arg.UserName,
arg.MemberName,
arg.SortNumber,
)
return err
}
const updateSpaceIsDefault = `-- name: UpdateSpaceIsDefault :exec
UPDATE space_member SET is_default = $1
WHERE space_id = $2 AND user_id = $3
`
type UpdateSpaceIsDefaultParams struct {
IsDefault int16
SpaceID int64
UserID int64
}
func (q *Queries) UpdateSpaceIsDefault(ctx context.Context, arg UpdateSpaceIsDefaultParams) error {
_, err := q.db.Exec(ctx, updateSpaceIsDefault, arg.IsDefault, arg.SpaceID, arg.UserID)
return err
}
const updateSpaceMemberName = `-- name: UpdateSpaceMemberName :exec
UPDATE space_member SET member_name = $1
WHERE space_id = $2 AND user_id = $3
`
type UpdateSpaceMemberNameParams struct {
MemberName string
SpaceID int64
UserID int64
}
func (q *Queries) UpdateSpaceMemberName(ctx context.Context, arg UpdateSpaceMemberNameParams) error {
_, err := q.db.Exec(ctx, updateSpaceMemberName, arg.MemberName, arg.SpaceID, arg.UserID)
return err
}
+37 -17
View File
@@ -23,27 +23,47 @@ primary key,
create table space
(
id bigint not null
primary key,
name varchar(255) not null,
invite_code varchar(30) not null,
owner_id bigint not null,
create_time timestamp with time zone default now(),
update_time timestamp with time zone default now()
id bigint primary key,
-- 核心业务字段
name varchar(255) not null,
invite_code varchar(30) not null unique,
owner_id bigint not null,
-- 状态字段
status smallint default 0 not null, -- 0 active 1 disabled 2 dissolved
disabled_time timestamp with time zone,
dissolve_time timestamp with time zone,
-- 时间字段
create_time timestamp with time zone default now() not null,
update_time timestamp with time zone default now() not null
);
create table space_member
(
id bigint not null
primary key,
space_id bigint not null,
user_id bigint not null,
role smallint default 2 not null,
is_default smallint default 0 not null,
user_name varchar(255) not null,
sort_number integer default 0 not null,
join_time timestamp with time zone default now(),
update_time timestamp with time zone default now()
id bigint primary key,
-- 关系核心字段
space_id bigint not null,
user_id bigint not null,
-- 角色 & 默认
role smallint default 2 not null,
is_default smallint default 0 not null,
sort_number integer default 0 not null,
member_name varchar(255) not null,
-- 成员状态
status smallint default 0 not null, -- 0 active 1 disabled 2 exited
disabled_time timestamp with time zone,
exit_time timestamp with time zone,
-- 时间字段
join_time timestamp with time zone default now() not null,
update_time timestamp with time zone default now() not null,
unique (space_id, user_id)
);
create table finance_category_default
-2
View File
@@ -56,10 +56,8 @@
"react-native-screens": "~4.16.0",
"react-native-sortables": "^1.9.3",
"react-native-svg": "15.12.1",
"react-native-toast-message": "^2.3.3",
"react-native-web": "^0.21.0",
"react-native-worklets": "0.5.1",
"react-native-z-view": "^0.2.4",
"sonner-native": "^0.23.1",
"tailwind-merge": "^3.4.0",
"victory-native": "^37.3.6",
-30
View File
@@ -146,18 +146,12 @@ importers:
react-native-svg:
specifier: 15.12.1
version: 15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react-native-toast-message:
specifier: ^2.3.3
version: 2.3.3(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react-native-web:
specifier: ^0.21.0
version: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-native-worklets:
specifier: 0.5.1
version: 0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react-native-z-view:
specifier: ^0.2.4
version: 0.2.4(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
sonner-native:
specifier: ^0.23.1
version: 0.23.1(react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.2(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native-svg@15.12.1(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
@@ -5123,12 +5117,6 @@ packages:
react: '*'
react-native: '*'
react-native-toast-message@2.3.3:
resolution: {integrity: sha512-4IIUHwUPvKHu4gjD0Vj2aGQzqPATiblL1ey8tOqsxOWRPGGu52iIbL8M/mCz4uyqecvPdIcMY38AfwRuUADfQQ==}
peerDependencies:
react: '*'
react-native: '*'
react-native-web@0.21.2:
resolution: {integrity: sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==}
peerDependencies:
@@ -5142,13 +5130,6 @@ packages:
react: '*'
react-native: '*'
react-native-z-view@0.2.4:
resolution: {integrity: sha512-K/z6GhHmDsyEZAy6ib8DEmITSyPb0rCJ95IZjilZbAarx8xB6prdvHvtgmCg67nFKO/l/ldBuYhvxQmClda+pQ==}
peerDependencies:
react: '*'
react-native: '*'
react-native-screens: '*'
react-native@0.81.5:
resolution: {integrity: sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==}
engines: {node: '>= 20.19.4'}
@@ -12365,11 +12346,6 @@ snapshots:
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
warn-once: 0.1.1
react-native-toast-message@2.3.3(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@babel/runtime': 7.28.4
@@ -12404,12 +12380,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
react-native-z-view@0.2.4(react-native-screens@4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0):
dependencies:
react: 19.1.0
react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
react-native-screens: 4.16.0(react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0):
dependencies:
'@jest/create-cache-key-function': 29.7.0
+1 -1
View File
@@ -127,7 +127,7 @@ export default function Auth() {
// avatar: loginRes.data.avatar,
// })
// router.push('/auth/space')
// router.push('/space/manage')
// }
// }
// catch (err) {
@@ -0,0 +1,179 @@
import type { UserSpace } from '@/types/space'
import { useEffect, useState } from 'react'
import { Text, View } from 'react-native'
import { toast } from 'sonner-native'
import { LucideIcon } from '@/components/common/ao'
import { AInput, AModal } from '@/components/common/ao/index'
import {
Actionsheet,
ActionsheetBackdrop,
ActionsheetContent,
ActionsheetDragIndicator,
ActionsheetDragIndicatorWrapper,
ActionsheetIcon,
ActionsheetItem,
ActionsheetItemText,
} from '@/components/ui/actionsheet'
import { dissolveSpaceApi } from '@/request/api/space'
import { useUserStore } from '@/store/user'
interface SettingActionsProps {
isOpen: boolean
onClose: () => void
space: UserSpace | null
onActionSuccess?: () => void
}
export default function SettingActions(
{
isOpen,
onClose,
space,
onActionSuccess
}: SettingActionsProps,
) {
const userStore = useUserStore()
const [localSpace, setLocalSpace] = useState(space)
const [showNameModal, setShowNameModal] = useState(false)
const [showMemberNameModal, setShowMemberNameModal] = useState(false)
useEffect(() => {
setLocalSpace(space)
}, [space])
return (
<>
<Actionsheet
isOpen={isOpen}
onClose={() => {
onClose()
}}
>
<ActionsheetBackdrop />
<ActionsheetContent
style={{
paddingBottom: 6,
}}
>
<ActionsheetDragIndicatorWrapper>
<ActionsheetDragIndicator />
</ActionsheetDragIndicatorWrapper>
{
space?.role === 0 && (
<ActionsheetItem>
<ActionsheetIcon
as={() => <LucideIcon icon="UserCog" />}
/>
<ActionsheetItemText>成员管理</ActionsheetItemText>
</ActionsheetItem>
)
}
{
space?.owner_id === userStore.userInfo.id && (
<ActionsheetItem onPress={() => {
setShowNameModal(true)
}}
>
<ActionsheetIcon
as={() => <LucideIcon icon="Pencil" />}
/>
<ActionsheetItemText>修改空间名称</ActionsheetItemText>
</ActionsheetItem>
)
}
<ActionsheetItem>
<ActionsheetIcon
as={() => <LucideIcon icon="Pencil" />}
/>
<ActionsheetItemText>修改我的空间昵称</ActionsheetItemText>
</ActionsheetItem>
<ActionsheetItem>
<ActionsheetIcon
as={() => <LucideIcon icon="Star" />}
/>
<ActionsheetItemText>
{space?.is_default === 1 ? '取消' : '设置'}
默认
</ActionsheetItemText>
</ActionsheetItem>
<ActionsheetItem onPress={() => {
if (space?.owner_id === userStore.userInfo.id) {
// 解散空间
dissolveSpaceApi({
space_id: space.space_id,
}).then((res) => {
if (res.isSuccess) {
toast.success('空间已解散')
onActionSuccess?.
}
},
)
}
else {
// 退出空间
}
}}
>
<ActionsheetIcon
as={() => <LucideIcon icon="LogOut" />}
/>
<ActionsheetItemText>
{space?.owner_id === userStore.userInfo.id ? '解散' : '退出'}
空间
</ActionsheetItemText>
</ActionsheetItem>
</ActionsheetContent>
</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}
onCancel={() => {
setShowNameModal(false)
setLocalSpace(space)
}}
onClose={() => {
setShowNameModal(false)
setLocalSpace(space)
}}
onConfirm={async () => {
// 更新名称
if (!localSpace?.name) {
toast.warning('请输入空间名称')
}
}}
>
</AModal>
</>
)
}
@@ -1,5 +1,5 @@
import type { UserSpace } from '@/types/space'
import { useCallback, useEffect, useState } from 'react'
import { useEffect, useState } from 'react'
import { Pressable, Text, View } from 'react-native'
import Animated, { useAnimatedRef } from 'react-native-reanimated'
import Sortable from 'react-native-sortables'
@@ -11,12 +11,18 @@ import CustomHeader from '@/components/custom/Header'
import { Badge, BadgeText } from '@/components/ui/badge'
import { createSpaceApi, listUserSpacesApi } from '@/request/api/space/index'
import { useUserStore } from '@/store/user'
import SettingActions from './components/SettingActions'
export default function AuthSpace() {
const userStore = useUserStore()
// scrollView 相关
const scrollableRef = useAnimatedRef<Animated.ScrollView>()
const [scrollHeight, setScrollHeight] = useState(0)
const ITEM_HEIGHT = 64
// 空间列表
const [spaces, setSpaces] = useState<UserSpace[]>([])
const [selectedId, setSelectedId] = useState<number | null>(null)
// 新建
@@ -25,8 +31,11 @@ export default function AuthSpace() {
name: '',
})
const [spaces, setSpaces] = useState<UserSpace[]>([])
// 设置
const [showSettings, setShowSettings] = useState(false)
const [settingCurrentSpace, setSettingCurrentSpace] = useState<UserSpace | null>(null)
// 获取空间列表
function listUserSpaces() {
listUserSpacesApi().then((res) => {
if (res.isSuccess) {
@@ -36,12 +45,18 @@ export default function AuthSpace() {
}
const renderItem
= ({ item }: { item: UserSpace }) => (
= ({ item, index }: { item: UserSpace, index: number }) => (
<Pressable
key={item.id}
onPress={() => setSelectedId(item.id)}
key={item.space_id}
onPress={() => {
setSelectedId(item.space_id)
scrollToCenter(index)
}}
style={{
height: ITEM_HEIGHT,
}}
className={`rounded-md flex-row items-center justify-between px-4 py-3 border
${item.id === selectedId ? 'border-gray-700' : 'border-gray-300'}`}
${item.space_id === selectedId ? 'border-gray-700' : 'border-gray-300'}`}
>
<View className="flex-row items-center">
<Sortable.Handle>
@@ -83,7 +98,7 @@ export default function AuthSpace() {
<Text
className="text-xs"
>
{item.user_name}
{item.member_name}
</Text>
</View>
<View className="flex-row gap-1">
@@ -98,21 +113,55 @@ export default function AuthSpace() {
</View>
</View>
<LucideIcon
icon="Bolt"
size={20}
className="p-1"
/>
<View className="flex flex-row items-center gap-3">
<View className="w-8 h-8 rounded-full items-center justify-center bg-[#333333]">
<LucideIcon
icon="UserPlus"
size={18}
color="#fff"
/>
</View>
<Pressable
className="w-8 h-8 rounded-full items-center justify-center bg-[#333333]"
onPress={(e) => {
e.stopPropagation()
setSettingCurrentSpace(item)
setShowSettings(true)
}}
>
<LucideIcon
icon="Bolt"
size={18}
color="#fff"
clickable={false}
/>
</Pressable>
</View>
</Pressable>
)
const scrollToCenter = (index: number) => {
if (!scrollHeight)
return
const itemTop = index * ITEM_HEIGHT
const scrollY
= itemTop - scrollHeight / 2 + ITEM_HEIGHT / 2
scrollableRef.current?.scrollTo({
y: Math.max(0, scrollY),
animated: true,
})
}
useEffect(() => {
listUserSpaces()
}, [])
return (
<View className="flex-1 bg-white">
{/* Header */}
<CustomHeader title="空间列表" />
<View className="flex-1 px-4">
@@ -146,6 +195,9 @@ export default function AuthSpace() {
className="flex-1"
showsVerticalScrollIndicator={false}
ref={scrollableRef}
onLayout={(e) => {
setScrollHeight(e.nativeEvent.layout.height)
}}
>
{spaces.length === 0
? (
@@ -218,7 +270,7 @@ export default function AuthSpace() {
await createSpaceApi({
name: createForm.name,
user_name: userStore.userInfo.nickname,
member_name: userStore.userInfo.nickname,
sort_number: 0,
}).then((res) => {
if (res.isSuccess) {
@@ -227,11 +279,25 @@ export default function AuthSpace() {
setCreateForm({
name: '',
})
listUserSpaces()
}
})
}}
>
</AModal>
<SettingActions
isOpen={showSettings}
onClose={() => {
setShowSettings(false)
setTimeout(() => {
setSettingCurrentSpace(null)
}, 300)
}}
space={settingCurrentSpace}
/>
</View>
)
}
+4 -3
View File
@@ -2,9 +2,9 @@ import type { Method } from 'alova'
import type { AxiosError } from 'axios'
import { axiosRequestAdapter } from '@alova/adapter-axios'
import { createAlova } from 'alova'
import { toast } from 'sonner-native'
import { useNavStore } from '@/store/navigation'
import { useUserStore } from '@/store/user'
import { toast } from '@/utils/toast'
enum HttpCode {
Success = 1000,
@@ -28,8 +28,8 @@ function isWhiteListed(url: string) {
}
const alovaInstance = createAlova({
baseURL: 'http://192.168.5.6:8086/app',
// baseURL: 'http://192.168.1.137:8086/app',
// baseURL: 'http://192.168.5.6:8086/app',
baseURL: 'http://192.168.1.137:8086/app',
requestAdapter: axiosRequestAdapter(),
timeout: 5000,
cacheFor: null,
@@ -44,6 +44,7 @@ const alovaInstance = createAlova({
}
if (resData.code === HttpCode.Unauthorized) {
toast.error('登录状态已过期,请重新登录')
useUserStore.getState().clear()
useNavStore.getState().navigateToAuth()
return result
+12 -18
View File
@@ -23,28 +23,22 @@ const DEFAULT_NAV_MENUS: NavItem[] = [
{ label: '恋爱记', path: '/love', icon: 'Heart' },
{ label: '聊天', path: '/chat', icon: 'MessageCircle' },
{ label: '个人中心', path: '/user', icon: 'CircleUserRound' },
{ label: '我的空间', path: '/auth/space', icon: 'Earth' },
{ label: '我的空间', path: '/space/manage', icon: 'Earth' },
]
export const useNavStore = create<NavState>()(
persist(
(set, get) => ({
pathname: '/',
navMenus: DEFAULT_NAV_MENUS,
(set, get) => ({
pathname: '/',
navMenus: DEFAULT_NAV_MENUS,
setPathname: (p: string) => set({ pathname: p }),
navigateToAuth: () => {
const pathname = useNavStore.getState().pathname
setPathname: (p: string) => set({ pathname: p }),
navigateToAuth: () => {
const pathname = useNavStore.getState().pathname
if (pathname !== '/auth') {
router.replace('/auth')
}
},
setNavMenus: navMenus => set({ navMenus }),
}),
{
name: 'nav-storage',
storage: createJSONStorage(() => AsyncStorage),
if (pathname !== '/auth') {
router.replace('/auth')
}
},
),
setNavMenus: navMenus => set({ navMenus }),
}),
)
@@ -1,21 +1,22 @@
export interface UserSpace {
id: number
space_id: number
owner_id: number
name: string
role: number
member_count: number
is_default: number
user_name: string
member_name: string
sort_number: number
}
// 创建空间
export interface CreateSpaceParams {
name: string
user_name: string
member_name: string
sort_number: number
}
export interface CreateSpaceResult {
space_id: number
invite_code: string
@@ -37,6 +38,7 @@ export interface DissolveSpaceResult {
export interface JoinSpaceParams {
invite_code: string
}
export interface JoinSpaceResult {
space_id: number
space_name: string