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

No files matched your search

+1
View File
@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<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://$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" />
@@ -86,7 +86,7 @@ func CreateSpace(c fiber.Ctx) error {
UserID: userID, UserID: userID,
Role: 0, Role: 0,
IsDefault: int16(isDefault), IsDefault: int16(isDefault),
UserName: dto.UserName, MemberName: dto.MemberName,
SortNumber: dto.SortNumber, SortNumber: dto.SortNumber,
} }
@@ -114,6 +114,7 @@ func CreateSpace(c fiber.Ctx) error {
return responsex.OK(c, vo) return responsex.OK(c, vo)
} }
// ListUserSpaces 查询该用户的空间
func ListUserSpaces(c fiber.Ctx) error { func ListUserSpaces(c fiber.Ctx) error {
vo := new(types.ListSpacesVO) vo := new(types.ListSpacesVO)
@@ -133,13 +134,13 @@ func ListUserSpaces(c fiber.Ctx) error {
for _, row := range rows { for _, row := range rows {
userSpaces = append(userSpaces, types.UserSpace{ userSpaces = append(userSpaces, types.UserSpace{
Id: row.ID, SpaceId: row.SpaceID,
OwnerId: row.OwnerID, OwnerId: row.OwnerID,
Name: row.Name, Name: row.Name,
Role: row.Role, Role: row.Role,
MemberCount: row.MemberCount, MemberCount: row.MemberCount,
IsDefault: row.IsDefault, IsDefault: row.IsDefault,
UserName: row.UserName, MemberName: row.MemberName,
SortNumber: row.SortNumber, SortNumber: row.SortNumber,
}) })
} }
@@ -149,6 +150,127 @@ func ListUserSpaces(c fiber.Ctx) error {
return responsex.OK(c, vo) 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 { func initFinanceCategory(ctx context.Context, q *repo.Queries, spaceId int64, userId int64) error {
categoryDefaults, err := q.ListFinanceCategoryDefault(ctx) categoryDefaults, err := q.ListFinanceCategoryDefault(ctx)
if err != nil { if err != nil {
@@ -23,6 +23,11 @@ func SetupRouters(app *fiber.App) {
spaceRouter.Post("/create", space.CreateSpace) spaceRouter.Post("/create", space.CreateSpace)
spaceRouter.Post("/list", space.ListUserSpaces) 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) // authGroup.Get("/user/profile", user.GetProfile)
// spaceGroup.Get("/space/info", space.GetInfo) // spaceGroup.Get("/space/info", space.GetInfo)
+22 -3
View File
@@ -2,7 +2,7 @@ package types
type CreateSpaceDTO struct { type CreateSpaceDTO struct {
Name string `json:"name" validate:"required"` 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"` SortNumber int32 `json:"sort_number" validate:"required"`
} }
@@ -13,16 +13,35 @@ type CreateSpaceVO struct {
} }
type UserSpace struct { type UserSpace struct {
Id int64 `json:"id"` SpaceId int64 `json:"space_id"`
OwnerId int64 `json:"owner_id"` OwnerId int64 `json:"owner_id"`
Name string `json:"name"` Name string `json:"name"`
Role int16 `json:"role"` Role int16 `json:"role"`
MemberCount int64 `json:"member_count"` MemberCount int64 `json:"member_count"`
IsDefault int16 `json:"is_default"` IsDefault int16 `json:"is_default"`
UserName string `json:"user_name"` MemberName string `json:"member_name"`
SortNumber int32 `json:"sort_number"` SortNumber int32 `json:"sort_number"`
} }
type ListSpacesVO struct { type ListSpacesVO struct {
Spaces []UserSpace `json:"spaces"` 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"`
}
+12 -5
View File
@@ -9,9 +9,12 @@ INSERT INTO space (
id,name,invite_code,owner_id,create_time,update_time id,name,invite_code,owner_id,create_time,update_time
) VALUES ($1,$2,$3,$4,NOW(),NOW()); ) VALUES ($1,$2,$3,$4,NOW(),NOW());
-- 删除空间 -- name: DissolveSpace :exec
-- name: DeleteSpace :exec UPDATE space SET
DELETE FROM space WHERE id = $1; status = 2,
dissolve_time = NOW(),
update_time = NOW()
WHERE id = $1;
-- 根据邀请码获取 -- 根据邀请码获取
-- name: GetSpaceByInviteCode :one -- name: GetSpaceByInviteCode :one
@@ -19,13 +22,13 @@ SELECT * FROM space WHERE invite_code = $1;
-- name: ListSpacesByUserId :many -- name: ListSpacesByUserId :many
SELECT SELECT
s.id, s.id AS space_id,
s.owner_id, s.owner_id,
s.name, s.name,
sm.role, sm.role,
COALESCE(mc.member_count, 0) AS member_count, COALESCE(mc.member_count, 0) AS member_count,
sm.is_default, sm.is_default,
sm.user_name, sm.member_name,
sm.sort_number sm.sort_number
FROM space s FROM space s
JOIN space_member sm JOIN space_member sm
@@ -38,3 +41,7 @@ FROM space s
ON s.id = mc.space_id ON s.id = mc.space_id
WHERE sm.user_id = $1 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 WHERE user_id = $1
AND space_id = $2; AND space_id = $2;
-- 插入空间用户
-- name: InsertSpaceMember :exec -- 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()); VALUES ($1,$2,$3,$4,$5,$6,$7,NOW(),NOW());
-- 合计某个用户的空间数量
-- name: CountUserSpaces :one -- name: CountUserSpaces :one
SELECT COUNT(1) SELECT COUNT(1)
FROM space_member 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;
+7 -1
View File
@@ -36,6 +36,9 @@ type Space struct {
Name string Name string
InviteCode string InviteCode string
OwnerID int64 OwnerID int64
Status int16
DisabledTime pgtype.Timestamptz
DissolveTime pgtype.Timestamptz
CreateTime pgtype.Timestamptz CreateTime pgtype.Timestamptz
UpdateTime pgtype.Timestamptz UpdateTime pgtype.Timestamptz
} }
@@ -46,8 +49,11 @@ type SpaceMember struct {
UserID int64 UserID int64
Role int16 Role int16
IsDefault int16 IsDefault int16
UserName string
SortNumber int32 SortNumber int32
MemberName string
Status int16
DisabledTime pgtype.Timestamptz
ExitTime pgtype.Timestamptz
JoinTime pgtype.Timestamptz JoinTime pgtype.Timestamptz
UpdateTime pgtype.Timestamptz UpdateTime pgtype.Timestamptz
} }
+37 -13
View File
@@ -9,18 +9,21 @@ import (
"context" "context"
) )
const deleteSpace = `-- name: DeleteSpace :exec const dissolveSpace = `-- name: DissolveSpace :exec
DELETE FROM space WHERE id = $1 UPDATE space SET
status = 2,
dissolve_time = NOW(),
update_time = NOW()
WHERE id = $1
` `
// 删除空间 func (q *Queries) DissolveSpace(ctx context.Context, id int64) error {
func (q *Queries) DeleteSpace(ctx context.Context, id int64) error { _, err := q.db.Exec(ctx, dissolveSpace, id)
_, err := q.db.Exec(ctx, deleteSpace, id)
return err return err
} }
const getSpaceById = `-- name: GetSpaceById :one 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获取分享空间信息 // 根据id获取分享空间信息
@@ -32,6 +35,9 @@ func (q *Queries) GetSpaceById(ctx context.Context, id int64) (Space, error) {
&i.Name, &i.Name,
&i.InviteCode, &i.InviteCode,
&i.OwnerID, &i.OwnerID,
&i.Status,
&i.DisabledTime,
&i.DissolveTime,
&i.CreateTime, &i.CreateTime,
&i.UpdateTime, &i.UpdateTime,
) )
@@ -39,7 +45,7 @@ func (q *Queries) GetSpaceById(ctx context.Context, id int64) (Space, error) {
} }
const getSpaceByInviteCode = `-- name: GetSpaceByInviteCode :one 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.Name,
&i.InviteCode, &i.InviteCode,
&i.OwnerID, &i.OwnerID,
&i.Status,
&i.DisabledTime,
&i.DissolveTime,
&i.CreateTime, &i.CreateTime,
&i.UpdateTime, &i.UpdateTime,
) )
@@ -83,13 +92,13 @@ func (q *Queries) InsertSpace(ctx context.Context, arg InsertSpaceParams) error
const listSpacesByUserId = `-- name: ListSpacesByUserId :many const listSpacesByUserId = `-- name: ListSpacesByUserId :many
SELECT SELECT
s.id, s.id AS space_id,
s.owner_id, s.owner_id,
s.name, s.name,
sm.role, sm.role,
COALESCE(mc.member_count, 0) AS member_count, COALESCE(mc.member_count, 0) AS member_count,
sm.is_default, sm.is_default,
sm.user_name, sm.member_name,
sm.sort_number sm.sort_number
FROM space s FROM space s
JOIN space_member sm JOIN space_member sm
@@ -105,13 +114,13 @@ ORDER BY sm.is_default DESC, sm.sort_number ASC
` `
type ListSpacesByUserIdRow struct { type ListSpacesByUserIdRow struct {
ID int64 SpaceID int64
OwnerID int64 OwnerID int64
Name string Name string
Role int16 Role int16
MemberCount int64 MemberCount int64
IsDefault int16 IsDefault int16
UserName string MemberName string
SortNumber int32 SortNumber int32
} }
@@ -125,13 +134,13 @@ func (q *Queries) ListSpacesByUserId(ctx context.Context, userID int64) ([]ListS
for rows.Next() { for rows.Next() {
var i ListSpacesByUserIdRow var i ListSpacesByUserIdRow
if err := rows.Scan( if err := rows.Scan(
&i.ID, &i.SpaceID,
&i.OwnerID, &i.OwnerID,
&i.Name, &i.Name,
&i.Role, &i.Role,
&i.MemberCount, &i.MemberCount,
&i.IsDefault, &i.IsDefault,
&i.UserName, &i.MemberName,
&i.SortNumber, &i.SortNumber,
); err != nil { ); err != nil {
return nil, err return nil, err
@@ -143,3 +152,18 @@ func (q *Queries) ListSpacesByUserId(ctx context.Context, userID int64) ([]ListS
} }
return items, nil 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 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()) VALUES ($1,$2,$3,$4,$5,$6,$7,NOW(),NOW())
` `
@@ -53,7 +53,7 @@ type InsertSpaceMemberParams struct {
UserID int64 UserID int64
Role int16 Role int16
IsDefault int16 IsDefault int16
UserName string MemberName string
SortNumber int32 SortNumber int32
} }
@@ -64,8 +64,40 @@ func (q *Queries) InsertSpaceMember(ctx context.Context, arg InsertSpaceMemberPa
arg.UserID, arg.UserID,
arg.Role, arg.Role,
arg.IsDefault, arg.IsDefault,
arg.UserName, arg.MemberName,
arg.SortNumber, arg.SortNumber,
) )
return err 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
}
+30 -10
View File
@@ -23,27 +23,47 @@ primary key,
create table space create table space
( (
id bigint not null id bigint primary key,
primary key,
-- 核心业务字段
name varchar(255) not null, name varchar(255) not null,
invite_code varchar(30) not null, invite_code varchar(30) not null unique,
owner_id bigint not null, owner_id bigint not null,
create_time timestamp with time zone default now(),
update_time timestamp with time zone default now() -- 状态字段
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 create table space_member
( (
id bigint not null id bigint primary key,
primary key,
-- 关系核心字段
space_id bigint not null, space_id bigint not null,
user_id bigint not null, user_id bigint not null,
-- 角色 & 默认
role smallint default 2 not null, role smallint default 2 not null,
is_default smallint default 0 not null, is_default smallint default 0 not null,
user_name varchar(255) not null,
sort_number integer default 0 not null, sort_number integer default 0 not null,
join_time timestamp with time zone default now(), member_name varchar(255) not null,
update_time timestamp with time zone default now()
-- 成员状态
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 create table finance_category_default
-2
View File
@@ -56,10 +56,8 @@
"react-native-screens": "~4.16.0", "react-native-screens": "~4.16.0",
"react-native-sortables": "^1.9.3", "react-native-sortables": "^1.9.3",
"react-native-svg": "15.12.1", "react-native-svg": "15.12.1",
"react-native-toast-message": "^2.3.3",
"react-native-web": "^0.21.0", "react-native-web": "^0.21.0",
"react-native-worklets": "0.5.1", "react-native-worklets": "0.5.1",
"react-native-z-view": "^0.2.4",
"sonner-native": "^0.23.1", "sonner-native": "^0.23.1",
"tailwind-merge": "^3.4.0", "tailwind-merge": "^3.4.0",
"victory-native": "^37.3.6", "victory-native": "^37.3.6",
-30
View File
@@ -146,18 +146,12 @@ importers:
react-native-svg: react-native-svg:
specifier: 15.12.1 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) 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: react-native-web:
specifier: ^0.21.0 specifier: ^0.21.0
version: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) version: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-native-worklets: react-native-worklets:
specifier: 0.5.1 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) 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: sonner-native:
specifier: ^0.23.1 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) 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: '*'
react-native: '*' react-native: '*'
react-native-toast-message@2.3.3:
resolution: {integrity: sha512-4IIUHwUPvKHu4gjD0Vj2aGQzqPATiblL1ey8tOqsxOWRPGGu52iIbL8M/mCz4uyqecvPdIcMY38AfwRuUADfQQ==}
peerDependencies:
react: '*'
react-native: '*'
react-native-web@0.21.2: react-native-web@0.21.2:
resolution: {integrity: sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==} resolution: {integrity: sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==}
peerDependencies: peerDependencies:
@@ -5142,13 +5130,6 @@ packages:
react: '*' react: '*'
react-native: '*' 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: react-native@0.81.5:
resolution: {integrity: sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==} resolution: {integrity: sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==}
engines: {node: '>= 20.19.4'} 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) react-native: 0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0)
warn-once: 0.1.1 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): react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
'@babel/runtime': 7.28.4 '@babel/runtime': 7.28.4
@@ -12404,12 +12380,6 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - 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): react-native@0.81.5(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.1.0):
dependencies: dependencies:
'@jest/create-cache-key-function': 29.7.0 '@jest/create-cache-key-function': 29.7.0
+1 -1
View File
@@ -127,7 +127,7 @@ export default function Auth() {
// avatar: loginRes.data.avatar, // avatar: loginRes.data.avatar,
// }) // })
// router.push('/auth/space') // router.push('/space/manage')
// } // }
// } // }
// catch (err) { // 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 type { UserSpace } from '@/types/space'
import { useCallback, useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { Pressable, Text, View } from 'react-native' import { Pressable, Text, View } from 'react-native'
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'
@@ -11,12 +11,18 @@ import CustomHeader from '@/components/custom/Header'
import { Badge, BadgeText } from '@/components/ui/badge' import { Badge, BadgeText } from '@/components/ui/badge'
import { createSpaceApi, listUserSpacesApi } from '@/request/api/space/index' import { createSpaceApi, listUserSpacesApi } from '@/request/api/space/index'
import { useUserStore } from '@/store/user' import { useUserStore } from '@/store/user'
import SettingActions from './components/SettingActions'
export default function AuthSpace() { export default function AuthSpace() {
const userStore = useUserStore() const userStore = useUserStore()
// scrollView 相关
const scrollableRef = useAnimatedRef<Animated.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) const [selectedId, setSelectedId] = useState<number | null>(null)
// 新建 // 新建
@@ -25,8 +31,11 @@ export default function AuthSpace() {
name: '', name: '',
}) })
const [spaces, setSpaces] = useState<UserSpace[]>([]) // 设置
const [showSettings, setShowSettings] = useState(false)
const [settingCurrentSpace, setSettingCurrentSpace] = useState<UserSpace | null>(null)
// 获取空间列表
function listUserSpaces() { function listUserSpaces() {
listUserSpacesApi().then((res) => { listUserSpacesApi().then((res) => {
if (res.isSuccess) { if (res.isSuccess) {
@@ -36,12 +45,18 @@ export default function AuthSpace() {
} }
const renderItem const renderItem
= ({ item }: { item: UserSpace }) => ( = ({ item, index }: { item: UserSpace, index: number }) => (
<Pressable <Pressable
key={item.id} key={item.space_id}
onPress={() => setSelectedId(item.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 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"> <View className="flex-row items-center">
<Sortable.Handle> <Sortable.Handle>
@@ -83,7 +98,7 @@ export default function AuthSpace() {
<Text <Text
className="text-xs" className="text-xs"
> >
{item.user_name} {item.member_name}
</Text> </Text>
</View> </View>
<View className="flex-row gap-1"> <View className="flex-row gap-1">
@@ -98,21 +113,55 @@ export default function AuthSpace() {
</View> </View>
</View> </View>
<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 <LucideIcon
icon="Bolt" icon="Bolt"
size={20} size={18}
className="p-1" color="#fff"
clickable={false}
/> />
</Pressable> </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(() => { useEffect(() => {
listUserSpaces() listUserSpaces()
}, []) }, [])
return ( return (
<View className="flex-1 bg-white"> <View className="flex-1 bg-white">
{/* Header */}
<CustomHeader title="空间列表" /> <CustomHeader title="空间列表" />
<View className="flex-1 px-4"> <View className="flex-1 px-4">
@@ -146,6 +195,9 @@ export default function AuthSpace() {
className="flex-1" className="flex-1"
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
ref={scrollableRef} ref={scrollableRef}
onLayout={(e) => {
setScrollHeight(e.nativeEvent.layout.height)
}}
> >
{spaces.length === 0 {spaces.length === 0
? ( ? (
@@ -218,7 +270,7 @@ export default function AuthSpace() {
await createSpaceApi({ await createSpaceApi({
name: createForm.name, name: createForm.name,
user_name: userStore.userInfo.nickname, member_name: userStore.userInfo.nickname,
sort_number: 0, sort_number: 0,
}).then((res) => { }).then((res) => {
if (res.isSuccess) { if (res.isSuccess) {
@@ -227,11 +279,25 @@ export default function AuthSpace() {
setCreateForm({ setCreateForm({
name: '', name: '',
}) })
listUserSpaces()
} }
}) })
}} }}
> >
</AModal> </AModal>
<SettingActions
isOpen={showSettings}
onClose={() => {
setShowSettings(false)
setTimeout(() => {
setSettingCurrentSpace(null)
}, 300)
}}
space={settingCurrentSpace}
/>
</View> </View>
) )
} }
+4 -3
View File
@@ -2,9 +2,9 @@ import type { Method } from 'alova'
import type { AxiosError } from 'axios' import type { AxiosError } from 'axios'
import { axiosRequestAdapter } from '@alova/adapter-axios' import { axiosRequestAdapter } from '@alova/adapter-axios'
import { createAlova } from 'alova' import { createAlova } from 'alova'
import { toast } from 'sonner-native'
import { useNavStore } from '@/store/navigation' import { useNavStore } from '@/store/navigation'
import { useUserStore } from '@/store/user' import { useUserStore } from '@/store/user'
import { toast } from '@/utils/toast'
enum HttpCode { enum HttpCode {
Success = 1000, Success = 1000,
@@ -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,
@@ -44,6 +44,7 @@ const alovaInstance = createAlova({
} }
if (resData.code === HttpCode.Unauthorized) { if (resData.code === HttpCode.Unauthorized) {
toast.error('登录状态已过期,请重新登录')
useUserStore.getState().clear() useUserStore.getState().clear()
useNavStore.getState().navigateToAuth() useNavStore.getState().navigateToAuth()
return result return result
@@ -23,11 +23,10 @@ const DEFAULT_NAV_MENUS: NavItem[] = [
{ label: '恋爱记', path: '/love', icon: 'Heart' }, { label: '恋爱记', path: '/love', icon: 'Heart' },
{ label: '聊天', path: '/chat', icon: 'MessageCircle' }, { label: '聊天', path: '/chat', icon: 'MessageCircle' },
{ label: '个人中心', path: '/user', icon: 'CircleUserRound' }, { label: '个人中心', path: '/user', icon: 'CircleUserRound' },
{ label: '我的空间', path: '/auth/space', icon: 'Earth' }, { label: '我的空间', path: '/space/manage', icon: 'Earth' },
] ]
export const useNavStore = create<NavState>()( export const useNavStore = create<NavState>()(
persist(
(set, get) => ({ (set, get) => ({
pathname: '/', pathname: '/',
navMenus: DEFAULT_NAV_MENUS, navMenus: DEFAULT_NAV_MENUS,
@@ -42,9 +41,4 @@ export const useNavStore = create<NavState>()(
}, },
setNavMenus: navMenus => set({ navMenus }), setNavMenus: navMenus => set({ navMenus }),
}), }),
{
name: 'nav-storage',
storage: createJSONStorage(() => AsyncStorage),
},
),
) )
@@ -1,21 +1,22 @@
export interface UserSpace { export interface UserSpace {
id: number space_id: number
owner_id: number owner_id: number
name: string name: string
role: number role: number
member_count: number member_count: number
is_default: number is_default: number
user_name: string member_name: string
sort_number: number sort_number: number
} }
// 创建空间 // 创建空间
export interface CreateSpaceParams { export interface CreateSpaceParams {
name: string name: string
user_name: string member_name: string
sort_number: number sort_number: number
} }
export interface CreateSpaceResult { export interface CreateSpaceResult {
space_id: number space_id: number
invite_code: string invite_code: string
@@ -37,6 +38,7 @@ export interface DissolveSpaceResult {
export interface JoinSpaceParams { export interface JoinSpaceParams {
invite_code: string invite_code: string
} }
export interface JoinSpaceResult { export interface JoinSpaceResult {
space_id: number space_id: number
space_name: string space_name: string