diff --git a/code/allapp/allapp-go/.idea/data_source_mapping.xml b/code/allapp/allapp-go/.idea/data_source_mapping.xml index 2d0befc2..8267c753 100644 --- a/code/allapp/allapp-go/.idea/data_source_mapping.xml +++ b/code/allapp/allapp-go/.idea/data_source_mapping.xml @@ -1,6 +1,8 @@ + + \ No newline at end of file diff --git a/code/allapp/allapp-go/.idea/sqldialects.xml b/code/allapp/allapp-go/.idea/sqldialects.xml index 3f3b4305..e19bb375 100644 --- a/code/allapp/allapp-go/.idea/sqldialects.xml +++ b/code/allapp/allapp-go/.idea/sqldialects.xml @@ -1,6 +1,8 @@ + + diff --git a/code/allapp/allapp-go/api/handler/auth/index.go b/code/allapp/allapp-go/api/handler/auth/index.go index ad775b09..d2ce5eb8 100644 --- a/code/allapp/allapp-go/api/handler/auth/index.go +++ b/code/allapp/allapp-go/api/handler/auth/index.go @@ -154,7 +154,7 @@ func handleThirdLogin( openid string, nickname string, avatar string, - loginType int32, // 0=微信 1=QQ + loginType int16, // 0=微信 1=QQ vo *types.LoginVO, ) error { user, err := db.Queries.FindUserByOpenID(c.Context(), repo.FindUserByOpenIDParams{ @@ -191,7 +191,7 @@ func handleThirdLogin( } // 注册用户 -func registerUser(c fiber.Ctx, openid, nickname, avatar string, loginType int32, vo *types.LoginVO) error { +func registerUser(c fiber.Ctx, openid, nickname, avatar string, loginType int16, vo *types.LoginVO) error { userId := uniqueid.NextId() if nickname == "" { diff --git a/code/allapp/allapp-go/api/handler/space/index.go b/code/allapp/allapp-go/api/handler/space/index.go new file mode 100644 index 00000000..1bb1e6f6 --- /dev/null +++ b/code/allapp/allapp-go/api/handler/space/index.go @@ -0,0 +1,130 @@ +package space + +import ( + "allapp/api/router/middleware" + "allapp/api/types" + "allapp/db" + "allapp/db/repo" + "allapp/utils" + "allapp/utils/errorx" + "allapp/utils/requestx" + "allapp/utils/responsex" + "allapp/utils/uniqueid" + "context" + + "github.com/gofiber/fiber/v3" +) + +// CreateSpace 创建空间 +func CreateSpace(c fiber.Ctx) error { + dto := new(types.CreateSpaceDTO) + vo := new(types.CreateSpaceVO) + + if err := requestx.BindAndValidate(c, dto); err != nil { + return err + } + + userID, err2 := middleware.GetUserID(c) + + if err2 != nil { + return err2 + } + + var inviteCode string + + for { + // 生成邀请码 + code := utils.GenerateCode(6) + + // 检查数据库是否存在 + + _, err := db.Queries.GetSpaceByInviteCode(c.Context(), code) + + if err != nil { + if errorx.IsNotFound(err) { + inviteCode = code + break + } else { + return err + } + } + } + + spaceId := uniqueid.NextId() + + err := db.WithTx(c.Context(), func(q *repo.Queries) error { + // 创建空间 + params := repo.InsertSpaceParams{ + ID: spaceId, + Name: dto.Name, + InviteCode: inviteCode, + } + + err := q.InsertSpace(c.Context(), params) + + if err != nil { + return err + } + + // 将创建用户默认添加到该空间内 + spaceMemberParams := repo.InsertSpaceMemberParams{ + ID: uniqueid.NextId(), + SpaceID: spaceId, + UserID: userID, + Role: 0, + } + + err = q.InsertSpaceMember(c.Context(), spaceMemberParams) + if err != nil { + return err + } + + // 初始化记账 + err = initFinanceCategory(c.Context(), q, spaceId, userID) + if err != nil { + return err + } + + return nil + }) + + if err != nil { + return err + } + + vo.SpaceId = spaceId + vo.InviteCode = inviteCode + + return responsex.OK(c, vo) +} + +func initFinanceCategory(ctx context.Context, q *repo.Queries, spaceId int64, userId int64) error { + categoryDefaults, err := q.ListFinanceCategoryDefault(ctx) + if err != nil { + return err + } + if len(categoryDefaults) == 0 { + return nil + } + + categorySpaces := make([]repo.BatchInsertFinanceCategorySpaceParams, 0, len(categoryDefaults)) + + for _, sysCat := range categoryDefaults { + categorySpaces = append(categorySpaces, repo.BatchInsertFinanceCategorySpaceParams{ + ID: uniqueid.NextId(), + SpaceID: spaceId, + Name: sysCat.Name, + Icon: sysCat.Icon, + Type: sysCat.Type, + SortNumber: sysCat.SortNumber, + CreateBy: userId, + UpdateBy: userId, + }) + } + + if _, err := q.BatchInsertFinanceCategorySpace(ctx, categorySpaces); err != nil { + return err + } + + return nil +} diff --git a/code/allapp/allapp-go/api/router/middleware/auth.go b/code/allapp/allapp-go/api/router/middleware/auth.go index 770e563f..f51cc16c 100644 --- a/code/allapp/allapp-go/api/router/middleware/auth.go +++ b/code/allapp/allapp-go/api/router/middleware/auth.go @@ -13,7 +13,6 @@ const CtxUserIDKey = "user_id" // Auth 登录校验中间件 func Auth() fiber.Handler { return func(c fiber.Ctx) error { - token := strings.TrimSpace(c.Get("Token")) if token == "" { return responsex.Unauthorized(c, "未登录") diff --git a/code/allapp/allapp-go/api/router/router.go b/code/allapp/allapp-go/api/router/router.go index fbe86443..21c1f462 100644 --- a/code/allapp/allapp-go/api/router/router.go +++ b/code/allapp/allapp-go/api/router/router.go @@ -2,6 +2,8 @@ package router import ( "allapp/api/handler/auth" + "allapp/api/handler/space" + "allapp/api/router/middleware" "allapp/config" "github.com/gofiber/fiber/v3" @@ -14,7 +16,7 @@ func SetupRouters(app *fiber.App) { publicGroup := api.Group("") // 2️⃣ 需要登录 - //authGroup := api.Group("", middleware.Auth()) + authGroup := api.Group("", middleware.Auth()) // //// 3️⃣ 需要登录 + 需要空间 //spaceGroup := api.Group("", middleware.Auth(), middleware.SpaceGuard()) @@ -25,7 +27,8 @@ func SetupRouters(app *fiber.App) { authRouter.Post("/login/qq", auth.LoginQq) authRouter.Post("/login/wechat", auth.LoginWechat) - // 示例 + // 空间操作 + authGroup.Post("/space/create", space.CreateSpace) // authGroup.Get("/user/profile", user.GetProfile) // spaceGroup.Get("/space/info", space.GetInfo) } diff --git a/code/allapp/allapp-go/api/types/space.go b/code/allapp/allapp-go/api/types/space.go new file mode 100644 index 00000000..bba03711 --- /dev/null +++ b/code/allapp/allapp-go/api/types/space.go @@ -0,0 +1,10 @@ +package types + +type CreateSpaceDTO struct { + Name string `json:"name" validate:"required"` +} + +type CreateSpaceVO struct { + SpaceId int64 `json:"space_id"` + InviteCode string `json:"invite_code"` +} diff --git a/code/allapp/allapp-go/db/queries/finance_category_default.sql b/code/allapp/allapp-go/db/queries/finance_category_default.sql new file mode 100644 index 00000000..685635d2 --- /dev/null +++ b/code/allapp/allapp-go/db/queries/finance_category_default.sql @@ -0,0 +1,2 @@ +-- name: ListFinanceCategoryDefault :many +SELECT * FROM finance_category_default; diff --git a/code/allapp/allapp-go/db/queries/finance_category_space.sql b/code/allapp/allapp-go/db/queries/finance_category_space.sql new file mode 100644 index 00000000..a46c93a6 --- /dev/null +++ b/code/allapp/allapp-go/db/queries/finance_category_space.sql @@ -0,0 +1,3 @@ +-- name: BatchInsertFinanceCategorySpace :copyfrom +INSERT INTO finance_category_space (id, space_id, name, icon, type, sort_number,create_by,update_by) +VALUES ($1, $2, $3, $4, $5, $6,$7,$8); \ No newline at end of file diff --git a/code/allapp/allapp-go/db/queries/space.sql b/code/allapp/allapp-go/db/queries/space.sql index fee88a1d..a2f127f7 100644 --- a/code/allapp/allapp-go/db/queries/space.sql +++ b/code/allapp/allapp-go/db/queries/space.sql @@ -2,3 +2,17 @@ -- 根据id获取分享空间信息 -- name: GetSpaceById :one SELECT * FROM space WHERE id = $1; + +-- 创建空间 +-- name: InsertSpace :exec +INSERT INTO space ( + id,name,invite_code,create_time,update_time +) VALUES ($1,$2,$3,NOW(),NOW()); + +-- 删除空间 +-- name: DeleteSpace :exec +DELETE FROM space WHERE id = $1; + +-- 根据邀请码获取 +-- name: GetSpaceByInviteCode :one +SELECT * FROM space WHERE invite_code = $1; diff --git a/code/allapp/allapp-go/db/queries/space_member.sql b/code/allapp/allapp-go/db/queries/space_member.sql index 70717da4..60c72f93 100644 --- a/code/allapp/allapp-go/db/queries/space_member.sql +++ b/code/allapp/allapp-go/db/queries/space_member.sql @@ -3,4 +3,13 @@ SELECT role FROM space_member WHERE user_id = $1 - AND space_id = $2; \ No newline at end of file + AND space_id = $2; + +-- name: InsertSpaceMember :exec +INSERT INTO space_member(id,space_id,user_id,role,is_default,join_time,update_time) +VALUES ($1,$2,$3,$4,$5,NOW(),NOW()); + +-- name: CountUserSpaces :one +SELECT COUNT(1) +FROM space_member +WHERE user_id = $1; \ No newline at end of file diff --git a/code/allapp/allapp-go/db/repo/copyfrom.go b/code/allapp/allapp-go/db/repo/copyfrom.go new file mode 100644 index 00000000..9f57adde --- /dev/null +++ b/code/allapp/allapp-go/db/repo/copyfrom.go @@ -0,0 +1,49 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: copyfrom.go + +package repo + +import ( + "context" +) + +// iteratorForBatchInsertFinanceCategorySpace implements pgx.CopyFromSource. +type iteratorForBatchInsertFinanceCategorySpace struct { + rows []BatchInsertFinanceCategorySpaceParams + skippedFirstNextCall bool +} + +func (r *iteratorForBatchInsertFinanceCategorySpace) Next() bool { + if len(r.rows) == 0 { + return false + } + if !r.skippedFirstNextCall { + r.skippedFirstNextCall = true + return true + } + r.rows = r.rows[1:] + return len(r.rows) > 0 +} + +func (r iteratorForBatchInsertFinanceCategorySpace) Values() ([]interface{}, error) { + return []interface{}{ + r.rows[0].ID, + r.rows[0].SpaceID, + r.rows[0].Name, + r.rows[0].Icon, + r.rows[0].Type, + r.rows[0].SortNumber, + r.rows[0].CreateBy, + r.rows[0].UpdateBy, + }, nil +} + +func (r iteratorForBatchInsertFinanceCategorySpace) Err() error { + return nil +} + +func (q *Queries) BatchInsertFinanceCategorySpace(ctx context.Context, arg []BatchInsertFinanceCategorySpaceParams) (int64, error) { + return q.db.CopyFrom(ctx, []string{"finance_category_space"}, []string{"id", "space_id", "name", "icon", "type", "sort_number", "create_by", "update_by"}, &iteratorForBatchInsertFinanceCategorySpace{rows: arg}) +} diff --git a/code/allapp/allapp-go/db/repo/db.go b/code/allapp/allapp-go/db/repo/db.go index 71f6cab5..96d21c78 100644 --- a/code/allapp/allapp-go/db/repo/db.go +++ b/code/allapp/allapp-go/db/repo/db.go @@ -15,6 +15,7 @@ type DBTX interface { Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) Query(context.Context, string, ...interface{}) (pgx.Rows, error) QueryRow(context.Context, string, ...interface{}) pgx.Row + CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error) } func New(db DBTX) *Queries { diff --git a/code/allapp/allapp-go/db/repo/finance_category_default.sql.go b/code/allapp/allapp-go/db/repo/finance_category_default.sql.go new file mode 100644 index 00000000..584b9a0c --- /dev/null +++ b/code/allapp/allapp-go/db/repo/finance_category_default.sql.go @@ -0,0 +1,42 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: finance_category_default.sql + +package repo + +import ( + "context" +) + +const listFinanceCategoryDefault = `-- name: ListFinanceCategoryDefault :many +SELECT id, name, icon, type, sort_number, create_time, update_time FROM finance_category_default +` + +func (q *Queries) ListFinanceCategoryDefault(ctx context.Context) ([]FinanceCategoryDefault, error) { + rows, err := q.db.Query(ctx, listFinanceCategoryDefault) + if err != nil { + return nil, err + } + defer rows.Close() + var items []FinanceCategoryDefault + for rows.Next() { + var i FinanceCategoryDefault + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Icon, + &i.Type, + &i.SortNumber, + &i.CreateTime, + &i.UpdateTime, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/code/allapp/allapp-go/db/repo/finance_category_space.sql.go b/code/allapp/allapp-go/db/repo/finance_category_space.sql.go new file mode 100644 index 00000000..5d9735b4 --- /dev/null +++ b/code/allapp/allapp-go/db/repo/finance_category_space.sql.go @@ -0,0 +1,17 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.30.0 +// source: finance_category_space.sql + +package repo + +type BatchInsertFinanceCategorySpaceParams struct { + ID int64 + SpaceID int64 + Name string + Icon string + Type int16 + SortNumber int32 + CreateBy int64 + UpdateBy int64 +} diff --git a/code/allapp/allapp-go/db/repo/models.go b/code/allapp/allapp-go/db/repo/models.go index 641af819..9a52fcb8 100644 --- a/code/allapp/allapp-go/db/repo/models.go +++ b/code/allapp/allapp-go/db/repo/models.go @@ -8,11 +8,33 @@ import ( "github.com/jackc/pgx/v5/pgtype" ) +type FinanceCategoryDefault struct { + ID int64 + Name string + Icon string + Type int16 + SortNumber int32 + CreateTime pgtype.Timestamptz + UpdateTime pgtype.Timestamptz +} + +type FinanceCategorySpace struct { + ID int64 + SpaceID int64 + Name string + Icon string + Type int16 + SortNumber int32 + CreateBy int64 + CreateTime pgtype.Timestamptz + UpdateBy int64 + UpdateTime pgtype.Timestamptz +} + type Space struct { ID int64 Name string InviteCode string - OwnerID int64 CreateTime pgtype.Timestamptz UpdateTime pgtype.Timestamptz } @@ -22,6 +44,7 @@ type SpaceMember struct { SpaceID int64 UserID int64 Role int16 + IsDefault int16 JoinTime pgtype.Timestamptz UpdateTime pgtype.Timestamptz } @@ -30,7 +53,7 @@ type User struct { ID int64 Nickname string Avatar string - Status int32 + Status int16 LastLoginTime pgtype.Timestamptz CreateTime pgtype.Timestamptz UpdateTime pgtype.Timestamptz @@ -39,7 +62,7 @@ type User struct { type UserOauth struct { ID int64 UserID int64 - Type int32 + Type int16 Openid string CreateTime pgtype.Timestamptz UpdateTime pgtype.Timestamptz diff --git a/code/allapp/allapp-go/db/repo/space.sql.go b/code/allapp/allapp-go/db/repo/space.sql.go index 4653fe37..4d7e4896 100644 --- a/code/allapp/allapp-go/db/repo/space.sql.go +++ b/code/allapp/allapp-go/db/repo/space.sql.go @@ -9,8 +9,18 @@ import ( "context" ) +const deleteSpace = `-- name: DeleteSpace :exec +DELETE FROM space WHERE id = $1 +` + +// 删除空间 +func (q *Queries) DeleteSpace(ctx context.Context, id int64) error { + _, err := q.db.Exec(ctx, deleteSpace, 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, create_time, update_time FROM space WHERE id = $1 ` // 根据id获取分享空间信息 @@ -21,9 +31,44 @@ func (q *Queries) GetSpaceById(ctx context.Context, id int64) (Space, error) { &i.ID, &i.Name, &i.InviteCode, - &i.OwnerID, &i.CreateTime, &i.UpdateTime, ) return i, err } + +const getSpaceByInviteCode = `-- name: GetSpaceByInviteCode :one +SELECT id, name, invite_code, create_time, update_time FROM space WHERE invite_code = $1 +` + +// 根据邀请码获取 +func (q *Queries) GetSpaceByInviteCode(ctx context.Context, inviteCode string) (Space, error) { + row := q.db.QueryRow(ctx, getSpaceByInviteCode, inviteCode) + var i Space + err := row.Scan( + &i.ID, + &i.Name, + &i.InviteCode, + &i.CreateTime, + &i.UpdateTime, + ) + return i, err +} + +const insertSpace = `-- name: InsertSpace :exec +INSERT INTO space ( + id,name,invite_code,create_time,update_time +) VALUES ($1,$2,$3,NOW(),NOW()) +` + +type InsertSpaceParams struct { + ID int64 + Name string + InviteCode string +} + +// 创建空间 +func (q *Queries) InsertSpace(ctx context.Context, arg InsertSpaceParams) error { + _, err := q.db.Exec(ctx, insertSpace, arg.ID, arg.Name, arg.InviteCode) + return err +} diff --git a/code/allapp/allapp-go/db/repo/space_member.sql.go b/code/allapp/allapp-go/db/repo/space_member.sql.go index 0166b80e..ced96d79 100644 --- a/code/allapp/allapp-go/db/repo/space_member.sql.go +++ b/code/allapp/allapp-go/db/repo/space_member.sql.go @@ -9,6 +9,19 @@ import ( "context" ) +const countUserSpaces = `-- name: CountUserSpaces :one +SELECT COUNT(1) +FROM space_member +WHERE user_id = $1 +` + +func (q *Queries) CountUserSpaces(ctx context.Context, userID int64) (int64, error) { + row := q.db.QueryRow(ctx, countUserSpaces, userID) + var count int64 + err := row.Scan(&count) + return count, err +} + const getUserSpaceRole = `-- name: GetUserSpaceRole :one SELECT role FROM space_member @@ -28,3 +41,27 @@ func (q *Queries) GetUserSpaceRole(ctx context.Context, arg GetUserSpaceRolePara err := row.Scan(&role) return role, err } + +const insertSpaceMember = `-- name: InsertSpaceMember :exec +INSERT INTO space_member(id,space_id,user_id,role,is_default,join_time,update_time) +VALUES ($1,$2,$3,$4,$5,NOW(),NOW()) +` + +type InsertSpaceMemberParams struct { + ID int64 + SpaceID int64 + UserID int64 + Role int16 + IsDefault int16 +} + +func (q *Queries) InsertSpaceMember(ctx context.Context, arg InsertSpaceMemberParams) error { + _, err := q.db.Exec(ctx, insertSpaceMember, + arg.ID, + arg.SpaceID, + arg.UserID, + arg.Role, + arg.IsDefault, + ) + return err +} diff --git a/code/allapp/allapp-go/db/repo/user.sql.go b/code/allapp/allapp-go/db/repo/user.sql.go index b7fd8170..b354b693 100644 --- a/code/allapp/allapp-go/db/repo/user.sql.go +++ b/code/allapp/allapp-go/db/repo/user.sql.go @@ -26,15 +26,15 @@ LIMIT 1 type FindUserByOpenIDParams struct { Openid string - Type int32 + Type int16 } type FindUserByOpenIDRow struct { UserID int64 Nickname string Avatar string - Status int32 - LoginType int32 + Status int16 + LoginType int16 Openid string } @@ -70,7 +70,7 @@ type InsertUserParams struct { ID int64 Nickname string Avatar string - Status int32 + Status int16 } func (q *Queries) InsertUser(ctx context.Context, arg InsertUserParams) error { diff --git a/code/allapp/allapp-go/db/repo/user_oauth.sql.go b/code/allapp/allapp-go/db/repo/user_oauth.sql.go index 771e0a74..f6f4316d 100644 --- a/code/allapp/allapp-go/db/repo/user_oauth.sql.go +++ b/code/allapp/allapp-go/db/repo/user_oauth.sql.go @@ -25,7 +25,7 @@ INSERT INTO user_oauth ( type InsertUserOAuthParams struct { ID int64 UserID int64 - Type int32 + Type int16 Openid string } diff --git a/code/allapp/allapp-go/db/schema.sql b/code/allapp/allapp-go/db/schema.sql index 433a0fbf..127cc515 100644 --- a/code/allapp/allapp-go/db/schema.sql +++ b/code/allapp/allapp-go/db/schema.sql @@ -4,7 +4,7 @@ create table "user" primary key, nickname varchar(255) not null, avatar varchar(255) not null, - status int default 0 not null, + status smallint default 0 not null, last_login_time timestamp with time zone, create_time timestamp with time zone default now(), update_time timestamp with time zone default now() @@ -15,7 +15,7 @@ create table user_oauth id bigint not null primary key, user_id bigint not null, - type int default 0 not null, + type smallint default 0 not null, openid varchar(255) not null, create_time timestamp with time zone default now(), update_time timestamp with time zone default now() @@ -27,7 +27,6 @@ create table space 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() ); @@ -39,6 +38,36 @@ create table space_member space_id bigint not null, user_id bigint not null, role smallint default 2 not null, + is_default smallint default 0 not null, join_time timestamp with time zone default now(), update_time timestamp with time zone default now() ); + +create table finance_category_default +( + id bigint not null + primary key, + name varchar(64) not null, + icon varchar(64) not null, + type smallint default 0 not null, + sort_number integer default 0 not null, + create_time timestamp with time zone default now(), + update_time timestamp with time zone default now() +); + +create table finance_category_space +( + id bigint not null + primary key, + space_id bigint not null, + name varchar(64) not null, + icon varchar(64) not null, + type smallint default 0 not null, + sort_number integer default 0 not null, + create_by bigint not null, + create_time timestamp with time zone default now(), + update_by bigint not null, + update_time timestamp with time zone default now() +); + + diff --git a/code/allapp/allapp-go/utils/index.go b/code/allapp/allapp-go/utils/index.go index 9a02a40e..1b65c1ae 100644 --- a/code/allapp/allapp-go/utils/index.go +++ b/code/allapp/allapp-go/utils/index.go @@ -1,6 +1,10 @@ package utils -import "encoding/json" +import ( + "crypto/rand" + "encoding/json" + "math/big" +) // JSONStringToStruct 将 JSON 字符串反序列化到指定结构体类型 func JSONStringToStruct[T any](jsonStr string) (*T, error) { @@ -18,3 +22,24 @@ func JSONStringToMap(jsonStr string) (map[string]interface{}, error) { } return result, nil } + +// GenerateCode 生成指定长度的随机邀请码(大写字母+数字) +// 不会返回错误,如果随机失败,会自动重试 +func GenerateCode(length int) string { + const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + code := make([]byte, length) + + for i := 0; i < length; i++ { + for { + num, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) + if err != nil { + // 随机失败,重试 + continue + } + code[i] = charset[num.Int64()] + break + } + } + + return string(code) +} diff --git a/code/allapp/allapp-go/utils/pgtypex/pgtypex.go b/code/allapp/allapp-go/utils/pgtypex/index.go similarity index 100% rename from code/allapp/allapp-go/utils/pgtypex/pgtypex.go rename to code/allapp/allapp-go/utils/pgtypex/index.go diff --git a/code/allapp/allapp-rn/package.json b/code/allapp/allapp-rn/package.json index a5985f19..f9fccdea 100644 --- a/code/allapp/allapp-rn/package.json +++ b/code/allapp/allapp-rn/package.json @@ -56,8 +56,11 @@ "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", "zustand": "^5.0.8" diff --git a/code/allapp/allapp-rn/pnpm-lock.yaml b/code/allapp/allapp-rn/pnpm-lock.yaml index 26be92a5..afade9dc 100644 --- a/code/allapp/allapp-rn/pnpm-lock.yaml +++ b/code/allapp/allapp-rn/pnpm-lock.yaml @@ -146,12 +146,21 @@ 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) tailwind-merge: specifier: ^3.4.0 version: 3.4.0 @@ -4126,48 +4135,56 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-gnu@1.30.2: resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.27.0: resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.27.0: resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.27.0: resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.27.0: resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==} @@ -5106,6 +5123,12 @@ 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: @@ -5119,6 +5142,13 @@ 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'} @@ -5387,6 +5417,17 @@ packages: snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + sonner-native@0.23.1: + resolution: {integrity: sha512-p4WRxVoTjLTeuLcvuyMNzgd81tEUS0ayoqXEFhi5GNU1okbvBpqU5GiUjv14DgiLUYXPUQ0qq+OdrFJM2j4j+w==} + peerDependencies: + react: '*' + react-native: '*' + react-native-gesture-handler: '>=2.16.1' + react-native-reanimated: '>=3.10.1' + react-native-safe-area-context: '>=4.10.5' + react-native-screens: '>=3.31.1' + react-native-svg: '>=15.6.0' + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -12324,6 +12365,11 @@ 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 @@ -12358,6 +12404,12 @@ 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 @@ -12675,6 +12727,16 @@ snapshots: dot-case: 3.0.4 tslib: 2.8.1 + sonner-native@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): + 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-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) + source-map-js@1.2.1: {} source-map-support@0.5.21: diff --git a/code/allapp/allapp-rn/src/app/(tabs)/chat.tsx b/code/allapp/allapp-rn/src/app/(tabs)/chat.tsx index a2ca591a..8235d937 100644 --- a/code/allapp/allapp-rn/src/app/(tabs)/chat.tsx +++ b/code/allapp/allapp-rn/src/app/(tabs)/chat.tsx @@ -1,34 +1,64 @@ -import { useRouter } from 'expo-router' -import { Text, View } from 'react-native' -import { ScrollView } from 'react-native-gesture-handler' -import { AButton, ACell, ACellGroup } from '@/components/common/ao' -import EmptyState from '@/components/common/EmptyStatus' -import { Grid, GridItem } from '@/components/ui/grid' -import { useMoneyCategoryStore } from '@/store/moneyCategory' -import { useSpaceStore } from '@/store/space' +import { useCallback } from 'react' +import { StyleSheet, Text, View } from 'react-native' +import Sortable from 'react-native-sortables' -export default function Demo() { - const router = useRouter() - const spaceStore = useSpaceStore() +const DATA = Array.from({ length: 10 }, (_, index) => `Item ${index + 1}`) - function NoSpaceTip() { - return ( - 创建 / 加入 空间 - } - /> - ) - } +export default function CustomHandleExample() { + const renderItem = useCallback( + ({ item }: { item: string }) => ( + + {item} + + + :: + + + + ), + [], + ) return ( - - { - spaceStore.inSpace() - ? () - : - } + + ) } + +const styles = StyleSheet.create({ + card: { + alignItems: 'center', + backgroundColor: '#2e8555', + borderRadius: 12, + flexDirection: 'row', + justifyContent: 'center', + padding: 24, + }, + container: { + padding: 16, + }, + handle: { + backgroundColor: 'rgba(0,0,0,0.2)', + borderRadius: 5, + padding: 10, + }, + handleText: { + color: 'white', + fontWeight: 'bold', + }, + text: { + color: 'white', + flex: 1, + fontWeight: 'bold', + }, +}) diff --git a/code/allapp/allapp-rn/src/app/_layout.tsx b/code/allapp/allapp-rn/src/app/_layout.tsx index e50dd828..ac0cdcd7 100644 --- a/code/allapp/allapp-rn/src/app/_layout.tsx +++ b/code/allapp/allapp-rn/src/app/_layout.tsx @@ -1,19 +1,19 @@ import type { ErrorBoundaryProps } from 'expo-router' -import { ToastProvider } from '@gluestack-ui/core/toast/creator' import { useFonts } from 'expo-font' import { Stack, usePathname } from 'expo-router' import * as SplashScreen from 'expo-splash-screen' import { StatusBar } from 'expo-status-bar' import { useEffect } from 'react' +import { Modal, View } from 'react-native' import { GestureHandlerRootView } from 'react-native-gesture-handler' import { KeyboardProvider } from 'react-native-keyboard-controller' import { MenuProvider } from 'react-native-popup-menu' +import { Toaster } from 'sonner-native' import { AButton } from '@/components/common/ao' import EmptyState from '@/components/common/EmptyStatus' import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider' import { useAppStore } from '@/store/app' import { useNavStore } from '@/store/navigation' -import { ToastProviderWrapper } from '@/utils/toast' import '@/styles/global.css' import 'react-native-reanimated' @@ -64,36 +64,31 @@ export default function RootLayout() { }, [fontsLoaded]) return ( - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + ) } diff --git a/code/allapp/allapp-rn/src/app/auth/index.tsx b/code/allapp/allapp-rn/src/app/auth/index.tsx index c5ccdb11..d86894c2 100644 --- a/code/allapp/allapp-rn/src/app/auth/index.tsx +++ b/code/allapp/allapp-rn/src/app/auth/index.tsx @@ -1,7 +1,7 @@ import { useEvent } from 'expo' -import ExpoQQ from 'expo-qq' +// import ExpoQQ from 'expo-qq' import { useRouter } from 'expo-router' -import ExpoWeChat from 'expo-wechat' +// import ExpoWeChat from 'expo-wechat' import { useEffect, useState } from 'react' import { Image, Text, View } from 'react-native' import { AButton, ACheckbox } from '@/components/common/ao' @@ -25,174 +25,174 @@ export default function Auth() { const userStore = useUserStore() const appStore = useAppStore() - const onLoginFinished = useEvent(ExpoQQ, 'onLoginFinished') - const onGetUserInfo = useEvent(ExpoQQ, 'onGetUserInfo') - const authResult = useEvent(ExpoWeChat, 'onAuthResult') + // const onLoginFinished = useEvent(ExpoQQ, 'onLoginFinished') + // const onGetUserInfo = useEvent(ExpoQQ, 'onGetUserInfo') + // const authResult = useEvent(ExpoWeChat, 'onAuthResult') - async function onQQLogin() { - setLoading(true) - setForm(val => ({ - ...val, - type: 1, - })) - await ExpoQQ.login(['get_user_info']) - } - - async function onWeChatLogin() { - setLoading(true) - - // 检查微信是否安装 - const isInstalled = await ExpoWeChat.isWXAppInstalled() - if (!isInstalled) { - toast.show({ - title: onLoginFinished?.errorMessage || '请先安装微信', - }) - setLoading(false) - return - } - - setForm(val => ({ - ...val, - type: 0, - })) - - ExpoWeChat.sendAuthRequest('snsapi_userinfo', 'state123') - } - - useEffect(() => { - ExpoQQ.init('102826474', '') - ExpoWeChat.registerApp('wxdab3e21a1f7e392f', '') - }, []) - - useEffect(() => { - if (!onLoginFinished) { - return - } - - if (!onLoginFinished.success) { - toast.show({ - title: onLoginFinished?.errorMessage || '授权失败,请检查手机是否安装 QQ 或确认授权', - }) - setLoading(false) - return - } - - const fetchUserInfo = async () => { - try { - const tokenInfo = await ExpoQQ.getLoginTokenInfo() - if (!tokenInfo.openId) { - toast.show({ - title: tokenInfo?.errorMessage || '授权失败,请检查手机是否安装 QQ 或确认授权', - }) - setLoading(false) - return - } - - setForm(val => ({ - ...val, - openid: String(tokenInfo.openId), - })) - - await ExpoQQ.sendGetUserInfoReq() - } - catch (e) { - console.error('QQ 登录未完成', e) - setLoading(false) - } - } - - fetchUserInfo() - }, [onLoginFinished]) - - // QQ登录 - useEffect(() => { - const handleLogin = async () => { - if (!onGetUserInfo) - return - - try { - const loginRes = await loginQqApi({ - openid: form.openid, - nickname: onGetUserInfo.nickname || '', - avatar: onGetUserInfo.figureurlQq_2 || onGetUserInfo.figureurl_2, - }) - - if (loginRes.isSuccess) { - userStore.setUserInfo({ - token: loginRes.data.token, - id: loginRes.data.userId, - nickname: loginRes.data.nickname, - avatar: loginRes.data.avatar, - }) - - router.push('/auth/space') - } - } - catch (err) { - console.error('登录接口出错', err) - } - finally { - setLoading(false) - } - } - - handleLogin() - }, [onGetUserInfo]) - - // 微信登录 - useEffect(() => { - if (!authResult) - return - - const handleLogin = async () => { - if (authResult.errorCode === 0) { - try { - const loginRes = await loginWxApi({ - code: authResult.code, - }) - - if (loginRes.isSuccess) { - userStore.setUserInfo({ - token: loginRes.data.token, - id: loginRes.data.userId, - nickname: loginRes.data.nickname, - avatar: loginRes.data.avatar, - }) - - router.push('/') - } - } - catch (err) { - console.error('登录接口出错', err) - } - finally { - setLoading(false) - } - } - else { - console.log('授权失败:', authResult.errorMessage) - setLoading(false) - } - } - - handleLogin() - }, [authResult]) - - // function testLogin() { - // // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo3NjY3ODU5ODIxNjUwNjEsImlhdCI6MTc2OTM0ODI0Nn0.17ZNaHD3HSLM8dVn_JQK-gvsG9MKj47IRYV9D4xZkzs - - // userStore.setUserInfo({ - // token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo3NzgxMTUwMzIxMDA5MzMsImlhdCI6MTc3MjExMDQ5N30.Ab7_YNwftJigktnPt8f-FwwcLeS45CkrH0jT2jLjRtA', - // id: 778115032100933, - // nickname: 'Zhang', - // avatar: 'http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100', - // }) - - // appStore.init() - - // router.push('/') + // async function onQQLogin() { + // setLoading(true) + // setForm(val => ({ + // ...val, + // type: 1, + // })) + // await ExpoQQ.login(['get_user_info']) // } + // async function onWeChatLogin() { + // setLoading(true) + + // // 检查微信是否安装 + // const isInstalled = await ExpoWeChat.isWXAppInstalled() + // if (!isInstalled) { + // toast.show({ + // title: onLoginFinished?.errorMessage || '请先安装微信', + // }) + // setLoading(false) + // return + // } + + // setForm(val => ({ + // ...val, + // type: 0, + // })) + + // ExpoWeChat.sendAuthRequest('snsapi_userinfo', 'state123') + // } + + // useEffect(() => { + // ExpoQQ.init('102826474', '') + // ExpoWeChat.registerApp('wxdab3e21a1f7e392f', '') + // }, []) + + // useEffect(() => { + // if (!onLoginFinished) { + // return + // } + + // if (!onLoginFinished.success) { + // toast.show({ + // title: onLoginFinished?.errorMessage || '授权失败,请检查手机是否安装 QQ 或确认授权', + // }) + // setLoading(false) + // return + // } + + // const fetchUserInfo = async () => { + // try { + // const tokenInfo = await ExpoQQ.getLoginTokenInfo() + // if (!tokenInfo.openId) { + // toast.show({ + // title: tokenInfo?.errorMessage || '授权失败,请检查手机是否安装 QQ 或确认授权', + // }) + // setLoading(false) + // return + // } + + // setForm(val => ({ + // ...val, + // openid: String(tokenInfo.openId), + // })) + + // await ExpoQQ.sendGetUserInfoReq() + // } + // catch (e) { + // console.error('QQ 登录未完成', e) + // setLoading(false) + // } + // } + + // fetchUserInfo() + // }, [onLoginFinished]) + + // // QQ登录 + // useEffect(() => { + // const handleLogin = async () => { + // if (!onGetUserInfo) + // return + + // try { + // const loginRes = await loginQqApi({ + // openid: form.openid, + // nickname: onGetUserInfo.nickname || '', + // avatar: onGetUserInfo.figureurlQq_2 || onGetUserInfo.figureurl_2, + // }) + + // if (loginRes.isSuccess) { + // userStore.setUserInfo({ + // token: loginRes.data.token, + // id: loginRes.data.userId, + // nickname: loginRes.data.nickname, + // avatar: loginRes.data.avatar, + // }) + + // router.push('/auth/space') + // } + // } + // catch (err) { + // console.error('登录接口出错', err) + // } + // finally { + // setLoading(false) + // } + // } + + // handleLogin() + // }, [onGetUserInfo]) + + // // 微信登录 + // useEffect(() => { + // if (!authResult) + // return + + // const handleLogin = async () => { + // if (authResult.errorCode === 0) { + // try { + // const loginRes = await loginWxApi({ + // code: authResult.code, + // }) + + // if (loginRes.isSuccess) { + // userStore.setUserInfo({ + // token: loginRes.data.token, + // id: loginRes.data.userId, + // nickname: loginRes.data.nickname, + // avatar: loginRes.data.avatar, + // }) + + // router.push('/') + // } + // } + // catch (err) { + // console.error('登录接口出错', err) + // } + // finally { + // setLoading(false) + // } + // } + // else { + // console.log('授权失败:', authResult.errorMessage) + // setLoading(false) + // } + // } + + // handleLogin() + // }, [authResult]) + + function testLogin() { + // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo3NjY3ODU5ODIxNjUwNjEsImlhdCI6MTc2OTM0ODI0Nn0.17ZNaHD3HSLM8dVn_JQK-gvsG9MKj47IRYV9D4xZkzs + + userStore.setUserInfo({ + token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjo3NzgxMTUwMzIxMDA5MzMsImlhdCI6MTc3MjExMDQ5N30.Ab7_YNwftJigktnPt8f-FwwcLeS45CkrH0jT2jLjRtA', + id: 778115032100933, + nickname: 'Zhang', + avatar: 'http://thirdqq.qlogo.cn/ek_qqapp/AQHVialeUibE42oV8RvTTVZ4tyRlPYeJrUibWTwtCPfV68E4rSzn9cw00GkXQ3QDTiawwhjoqQb7/100', + }) + + appStore.init() + + router.push('/') + } + return ( - } @@ -229,7 +229,7 @@ export default function Auth() { onPress={onQQLogin} > QQ - + */} } loading={loading} - // onPress={testLogin} + onPress={testLogin} > 模拟登录 diff --git a/code/allapp/allapp-rn/src/app/auth/space.tsx b/code/allapp/allapp-rn/src/app/auth/space.tsx index 786c9ad7..698bd0a1 100644 --- a/code/allapp/allapp-rn/src/app/auth/space.tsx +++ b/code/allapp/allapp-rn/src/app/auth/space.tsx @@ -1,61 +1,278 @@ -import { Text, View } from 'react-native' -import { AButton, LucideIcon } from '@/components/common/ao' +import type { Space } from '@/types/space/space' +import { useCallback, useState } from 'react' +import { Pressable, Text, View } from 'react-native' +import Animated, { useAnimatedRef } from 'react-native-reanimated' +import Sortable from 'react-native-sortables' +import { toast } from 'sonner-native' +import { AButton, ADivider, AInput, LucideIcon } from '@/components/common/ao' +import { AModal } from '@/components/common/ao/index' import EmptyState from '@/components/common/EmptyStatus' import CustomHeader from '@/components/custom/Header' - -// name renshu +import { Badge, BadgeText } from '@/components/ui/badge' +import { createSpaceApi } from '@/request/api/space/space' export default function AuthSpace() { - return ( - - - - - 你的空间 - 选择要进入的空间 - + const scrollableRef = useAnimatedRef() - - {/* - - */} + const [selectedId, setSelectedId] = useState(null) + // 新建 + const [createOpen, setCreateOpen] = useState(false) + const [createForm, setCreateForm] = useState({ + name: '', + }) + + const spaces: Space[] = [ + { + id: 1, + name: 'A 的空间', + role: 1, + member_count: 8, + is_default: 1, + sort_number: 1, + user_name: '张三', + }, + { + id: 2, + name: 'B 的空间', + role: 2, + member_count: 5, + is_default: 0, + sort_number: 2, + user_name: '李四', + }, + { + id: 3, + name: 'C 的空间', + role: 3, + member_count: 12, + is_default: 0, + sort_number: 3, + user_name: '王五', + }, + { + id: 4, + name: 'D 的空间', + role: 2, + member_count: 6, + is_default: 0, + sort_number: 4, + user_name: '赵六', + }, + { + id: 5, + name: 'E 的空间', + role: 1, + member_count: 3, + is_default: 0, + sort_number: 5, + user_name: '钱七', + }, + { + id: 6, + name: 'F 的空间', + role: 2, + member_count: 9, + is_default: 0, + sort_number: 6, + user_name: '孙八', + }, + { + id: 7, + name: 'G 的空间', + role: 3, + member_count: 4, + is_default: 0, + sort_number: 7, + user_name: '周九', + }, + ] + + const renderItem = useCallback( + ({ item }: { item: Space }) => ( + setSelectedId(item.id)} + className={`rounded-md flex-row items-center justify-between px-4 py-3 border + ${item.id === selectedId ? 'border-gray-700' : 'border-gray-300'}`} + > + + + + - - - - - + + + {item.name} + - - - A 的空间 - - 张三 · 8 名成员 - + + 创建者 + + + + 默认 + + + + + + + + 用户A + - - - + + + + 0 + - - 创建新空间 - 加入其他空间 + + + ), + [], + ) + + return ( + + {/* Header */} + + + + + + + 选择或创建空间 + + + } + onPress={() => setCreateOpen(true)} + > + 创建新空间 + + } + > + 加入其他空间 + + + + + + + {spaces.length === 0 + ? ( + + + + ) + : ( + + )} + + + + }>进入空间 + + + 空间名称 + { + setCreateForm(prev => ({ + ...prev, + name: val, + })) + }} + /> + + ) + } + isOpen={createOpen} + onCancel={() => { + setCreateOpen(false) + setCreateForm({ + name: '', + }) + }} + onClose={() => { + setCreateOpen(false) + setCreateForm({ + name: '', + }) + }} + onConfirm={async () => { + if (!createForm.name) { + toast.warning('请输入空间名称') + return + } + await createSpaceApi({ + name: createForm.name, + }).then((res) => { + console.log(res) + if (res.isSuccess) { + toast.success('创建成功') + setCreateOpen(false) + setCreateForm({ + name: '', + }) + + + } + }) + }} + > + ) } diff --git a/code/allapp/allapp-rn/src/components/common/ao/Button.tsx b/code/allapp/allapp-rn/src/components/common/ao/Button.tsx index 5dd4bb90..44816bf8 100644 --- a/code/allapp/allapp-rn/src/components/common/ao/Button.tsx +++ b/code/allapp/allapp-rn/src/components/common/ao/Button.tsx @@ -39,14 +39,9 @@ const Button: React.FC = ({ const isDisabled = disabled || loading const hasChildren = !!children - const renderIcon = ( - icon?: IconProp, - position: 'left' | 'right' = 'left', - ) => { - if (!icon) - return null - - if (loading && position === 'left') { + const renderLeftIcon = () => { + // 左侧 loading + if (loading) { const spinnerColor = variant === 'outline' ? 'rgba(0,0,0,0.6)' : 'rgba(255,255,255,0.9)' @@ -57,17 +52,25 @@ const Button: React.FC = ({ ) } - const node = typeof icon === 'function' - ? icon() - : icon + // 左侧 icon + if (!leftIcon) + return null + + const node = typeof leftIcon === 'function' ? leftIcon() : leftIcon return ( - + + {node} + + ) + } + + const renderRightIcon = () => { + if (!rightIcon) + return null + const node = typeof rightIcon === 'function' ? rightIcon() : rightIcon + return ( + {node} ) @@ -83,9 +86,9 @@ const Button: React.FC = ({ variant={variant} size={size} > - {renderIcon(leftIcon, 'left')} + {renderLeftIcon()} {children && {children}} - {renderIcon(rightIcon, 'right')} + {renderRightIcon()} ) } diff --git a/code/allapp/allapp-rn/src/components/common/ao/Modal.tsx b/code/allapp/allapp-rn/src/components/common/ao/Modal.tsx index 8516e420..dafa2699 100644 --- a/code/allapp/allapp-rn/src/components/common/ao/Modal.tsx +++ b/code/allapp/allapp-rn/src/components/common/ao/Modal.tsx @@ -1,7 +1,8 @@ import type { ViewStyle } from 'react-native' +import { useState } from 'react' import { Text } from 'react-native' +import { Heading } from '@/components/ui/heading' import { CloseIcon, Icon } from '@/components/ui/icon' - import { Modal, ModalBackdrop, @@ -19,7 +20,7 @@ interface AModalProps { body?: string | React.ReactNode footer?: React.ReactNode onClose?: () => void - onConfirm?: () => void + onConfirm?: () => Promise | void onCancel?: () => void showBackdrop?: boolean showCloseIcon?: boolean @@ -41,6 +42,29 @@ export default function AModal({ className, style, }: AModalProps) { + const [innerLoading, setInnerLoading] = useState(false) + + const handleConfirm = () => { + if (!onConfirm || innerLoading) + return + + try { + const result = onConfirm() + + // 只有当返回的是 Promise 才开启 loading + if (result && typeof (result as Promise).then === 'function') { + setInnerLoading(true) + + result.finally(() => { + setInnerLoading(false) + }) + } + } + catch (error) { + console.error(error) + } + } + return ( - {title && (typeof title === 'string' ? {title} : title)} + {title && (typeof title === 'string' ? {title} : title)} { showCloseIcon && ( @@ -76,11 +100,14 @@ export default function AModal({ action="secondary" className="mr-3" onPress={onClose} + size="sm" > 取消 确认 diff --git a/code/allapp/allapp-rn/src/components/ui/gluestack-ui-provider/index.tsx b/code/allapp/allapp-rn/src/components/ui/gluestack-ui-provider/index.tsx index 3453713e..94eb2a2b 100644 --- a/code/allapp/allapp-rn/src/components/ui/gluestack-ui-provider/index.tsx +++ b/code/allapp/allapp-rn/src/components/ui/gluestack-ui-provider/index.tsx @@ -1,26 +1,28 @@ -import React, { useEffect } from 'react'; -import { config } from './config'; -import { View, ViewProps } from 'react-native'; -import { OverlayProvider } from '@gluestack-ui/core/overlay/creator'; -import { ToastProvider } from '@gluestack-ui/core/toast/creator'; -import { useColorScheme } from 'nativewind'; +import type { ViewProps } from 'react-native' +import { OverlayProvider } from '@gluestack-ui/core/overlay/creator' +import { ToastProvider } from '@gluestack-ui/core/toast/creator' +import { useColorScheme } from 'nativewind' +import React, { useEffect } from 'react' +import { View } from 'react-native' +import { Toaster } from 'sonner-native' +import { config } from './config' -export type ModeType = 'light' | 'dark' | 'system'; +export type ModeType = 'light' | 'dark' | 'system' export function GluestackUIProvider({ mode = 'light', ...props }: { - mode?: ModeType; - children?: React.ReactNode; - style?: ViewProps['style']; + mode?: ModeType + children?: React.ReactNode + style?: ViewProps['style'] }) { - const { colorScheme, setColorScheme } = useColorScheme(); + const { colorScheme, setColorScheme } = useColorScheme() useEffect(() => { - setColorScheme(mode); + setColorScheme(mode) // eslint-disable-next-line react-hooks/exhaustive-deps - }, [mode]); + }, [mode]) return ( {props.children} - ); + ) } diff --git a/code/allapp/allapp-rn/src/request/index.ts b/code/allapp/allapp-rn/src/request/index.ts index 2d25f23a..40edf566 100644 --- a/code/allapp/allapp-rn/src/request/index.ts +++ b/code/allapp/allapp-rn/src/request/index.ts @@ -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, @@ -54,9 +54,7 @@ const alovaInstance = createAlova({ onError: (error: AxiosError) => { const status = error.status - const config = error.config - - console.warn(`[HTTP ${status}] ${config?.method?.toUpperCase()} ${config?.url}`) + console.log(error) switch (status) { case 400: diff --git a/code/allapp/allapp-rn/src/types/space/space.ts b/code/allapp/allapp-rn/src/types/space/space.ts index 71ab2898..88f0cca6 100644 --- a/code/allapp/allapp-rn/src/types/space/space.ts +++ b/code/allapp/allapp-rn/src/types/space/space.ts @@ -1,3 +1,14 @@ +export interface Space { + id: number + name: string + role: number + member_count: number + is_default: number + + user_name: string + sort_number: number +} + // 创建空间 export interface CreateSpaceParams { name: string diff --git a/code/allapp/allapp-rn/test.html b/code/allapp/allapp-rn/test.html index 1a79680a..5868e873 100644 --- a/code/allapp/allapp-rn/test.html +++ b/code/allapp/allapp-rn/test.html @@ -1,207 +1,67 @@ - - - - - -记账列表 - +import { useCallback } from 'react'; +import { StyleSheet, Text, View } from 'react-native'; +import Sortable from 'react-native-sortables'; - - - - - - - - - 本月统计 - - - 收入 - {{ totalIncome }} - - - 支出 - {{ totalExpense }} - - - 结余 - {{ balance }} - - - - - - - {{ group.date }} - - - - {{ item.category }} - {{ item.remark }} - - - {{ item.type === 'income' ? '+' : '-' }}{{ item.amount }} - - - - - - - - - - +});