u
This commit is contained in:
1 parent
e893ef2e48
commit
b6415c0d65
25 files changed
+1495
-112
No files matched your search
@@ -5,6 +5,7 @@ import (
|
|||||||
"allapp-go/internal/httpx"
|
"allapp-go/internal/httpx"
|
||||||
"allapp-go/internal/types"
|
"allapp-go/internal/types"
|
||||||
"allapp-go/pkg/db"
|
"allapp-go/pkg/db"
|
||||||
|
"allapp-go/pkg/uniqueid"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
)
|
)
|
||||||
@@ -48,3 +49,22 @@ func LoadDataBySql(c fiber.Ctx) error {
|
|||||||
|
|
||||||
return httpx.OK(c, data)
|
return httpx.OK(c, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetUniqueId(c fiber.Ctx) error {
|
||||||
|
var req types.GetUniqueIdReq
|
||||||
|
|
||||||
|
if err := httpx.BindAndValidate(c, &req); err != nil {
|
||||||
|
return errors.WithStack(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
count := req.Count
|
||||||
|
|
||||||
|
// 预分配,避免扩容
|
||||||
|
ids := make([]int64, 0, count)
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
ids = append(ids, uniqueid.NextId())
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpx.OK(c, ids)
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@ func SaveData(c fiber.Ctx) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ctx := c.Context()
|
ctx := c.Context()
|
||||||
|
|
||||||
dbClient := db.New()
|
dbClient := db.New()
|
||||||
|
|
||||||
err := dbClient.WithTx(ctx, func(tx *db.Client) error {
|
err := dbClient.WithTx(ctx, func(tx *db.Client) error {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package httpx
|
package httpx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
)
|
)
|
||||||
@@ -8,10 +10,23 @@ import (
|
|||||||
var validate = validator.New()
|
var validate = validator.New()
|
||||||
|
|
||||||
func BindAndValidate(c fiber.Ctx, v any) error {
|
func BindAndValidate(c fiber.Ctx, v any) error {
|
||||||
|
// 1️⃣ 绑定 JSON
|
||||||
if err := c.Bind().Body(v); err != nil {
|
if err := c.Bind().Body(v); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2️⃣ 只允许 struct 校验(先不支持 slice)
|
||||||
|
rv := reflect.ValueOf(v)
|
||||||
|
if rv.Kind() == reflect.Ptr {
|
||||||
|
rv = rv.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果是 slice,直接跳过校验
|
||||||
|
if rv.Kind() == reflect.Slice {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3️⃣ struct 校验
|
||||||
if err := validate.Struct(v); err != nil {
|
if err := validate.Struct(v); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"allapp-go/internal/httpx"
|
"context"
|
||||||
"allapp-go/pkg/jwtx"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
)
|
)
|
||||||
@@ -13,35 +11,30 @@ const CtxUserIDKey = "user_id"
|
|||||||
// Auth 登录校验中间件
|
// Auth 登录校验中间件
|
||||||
func Auth() fiber.Handler {
|
func Auth() fiber.Handler {
|
||||||
return func(c fiber.Ctx) error {
|
return func(c fiber.Ctx) error {
|
||||||
token := strings.TrimSpace(c.Get("Token"))
|
//token := strings.TrimSpace(c.Get("Token"))
|
||||||
|
//
|
||||||
if token == "" {
|
//if token == "" {
|
||||||
return httpx.Unauthorized(c, "未登录")
|
// return httpx.Unauthorized(c, "未登录")
|
||||||
}
|
//}
|
||||||
|
//
|
||||||
|
//verifyToken, ok := jwtx.VerifyToken(c.Context(), token)
|
||||||
|
//if !ok {
|
||||||
|
// return httpx.Unauthorized(c, "登录已过期")
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
//c.Locals(CtxUserIDKey, verifyToken.Data["user_id"])
|
||||||
|
|
||||||
verifyToken, ok := jwtx.VerifyToken(c.Context(), token)
|
ctx := context.WithValue(c.Context(), CtxUserIDKey, int64(779895419121733))
|
||||||
if !ok {
|
c.SetContext(ctx)
|
||||||
return httpx.Unauthorized(c, "登录已过期")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Locals(CtxUserIDKey, verifyToken.Data["user_id"])
|
|
||||||
|
|
||||||
return c.Next()
|
return c.Next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetUserID(c fiber.Ctx) (int64, error) {
|
func GetUserID(ctx context.Context) (int64, bool) {
|
||||||
v := c.Locals(CtxUserIDKey)
|
v := ctx.Value(CtxUserIDKey)
|
||||||
|
|
||||||
if v == nil {
|
id, ok := v.(int64)
|
||||||
return 0, httpx.Unauthorized(c, "登录已过期")
|
|
||||||
}
|
|
||||||
|
|
||||||
userID, ok := v.(int64)
|
return id, ok
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return 0, httpx.Unauthorized(c, "登录已过期")
|
|
||||||
}
|
|
||||||
|
|
||||||
return userID, nil
|
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,7 @@ package router
|
|||||||
import (
|
import (
|
||||||
"allapp-go/internal/config"
|
"allapp-go/internal/config"
|
||||||
"allapp-go/internal/handle"
|
"allapp-go/internal/handle"
|
||||||
|
"allapp-go/internal/middleware"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
)
|
)
|
||||||
@@ -11,10 +12,14 @@ func SetupRouter(app *fiber.App, cfg *config.Config) {
|
|||||||
api := app.Group(cfg.App.BaseUrl)
|
api := app.Group(cfg.App.BaseUrl)
|
||||||
|
|
||||||
// ==================== data ====================
|
// ==================== data ====================
|
||||||
api.Post("/data/loadData", handle.LoadData)
|
data := api.Group("/data", middleware.Auth())
|
||||||
api.Post("/data/loadDataBySql", handle.LoadDataBySql)
|
|
||||||
api.Post("/data/saveData", handle.SaveData)
|
|
||||||
|
|
||||||
|
data.Post("/loadData", handle.LoadData)
|
||||||
|
data.Post("/loadDataBySql", handle.LoadDataBySql)
|
||||||
|
data.Post("/saveData", handle.SaveData)
|
||||||
|
data.Post("/getUniqueId", handle.GetUniqueId)
|
||||||
|
|
||||||
|
// ==================== auth ====================
|
||||||
api.Post("/auth/login/qq", handle.LoginQq)
|
api.Post("/auth/login/qq", handle.LoginQq)
|
||||||
api.Post("/auth/login/wechat", handle.LoginWechat)
|
api.Post("/auth/login/wechat", handle.LoginWechat)
|
||||||
}
|
}
|
||||||
@@ -12,3 +12,7 @@ type LoadDataBySqlReq struct {
|
|||||||
Sql string `json:"sql" validate:"required"`
|
Sql string `json:"sql" validate:"required"`
|
||||||
Args []any `json:"args"`
|
Args []any `json:"args"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type GetUniqueIdReq struct {
|
||||||
|
Count int `json:"count" binding:"required,min=1,max=100"`
|
||||||
|
}
|
||||||
@@ -6,6 +6,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var auditExcludeTables = map[string]bool{
|
||||||
|
"user": true,
|
||||||
|
"user_oauth": true,
|
||||||
|
}
|
||||||
|
|
||||||
func validateData(table string, keyField string, data map[string]any) (any, error) {
|
func validateData(table string, keyField string, data map[string]any) (any, error) {
|
||||||
if table == "" {
|
if table == "" {
|
||||||
return nil, fmt.Errorf("表名不能为空")
|
return nil, fmt.Errorf("表名不能为空")
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"allapp-go/internal/middleware"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 构建 INSERT SQL(单条)
|
// 构建 INSERT SQL(单条)
|
||||||
@@ -43,6 +45,25 @@ func (c *Client) Insert(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !auditExcludeTables[table] {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
delete(data, "create_time")
|
||||||
|
delete(data, "update_time")
|
||||||
|
delete(data, "create_by")
|
||||||
|
delete(data, "update_by")
|
||||||
|
|
||||||
|
data["create_time"] = now
|
||||||
|
data["update_time"] = now
|
||||||
|
|
||||||
|
userID, flag := middleware.GetUserID(ctx)
|
||||||
|
|
||||||
|
if flag {
|
||||||
|
data["create_by"] = userID
|
||||||
|
data["update_by"] = userID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sql, args := buildInsertSQL(table, data)
|
sql, args := buildInsertSQL(table, data)
|
||||||
|
|
||||||
_, err := c.exec().Exec(ctx, sql, args...)
|
_, err := c.exec().Exec(ctx, sql, args...)
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"allapp-go/pkg/logger"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) LoadData(
|
func (c *Client) LoadData(
|
||||||
@@ -15,33 +18,32 @@ func (c *Client) LoadData(
|
|||||||
args []any,
|
args []any,
|
||||||
) ([]map[string]any, error) {
|
) ([]map[string]any, error) {
|
||||||
|
|
||||||
var where string
|
selectCols := "*"
|
||||||
|
if len(searchColumns) > 0 {
|
||||||
// WHERE 构造
|
selectCols = strings.Join(searchColumns, ", ")
|
||||||
if searchCondition != "" && len(searchColumns) > 0 {
|
|
||||||
var conditions []string
|
|
||||||
|
|
||||||
for _, col := range searchColumns {
|
|
||||||
conditions = append(conditions, fmt.Sprintf("%s LIKE ?", col))
|
|
||||||
}
|
|
||||||
|
|
||||||
where = " WHERE (" + strings.Join(conditions, " OR ") + ")"
|
|
||||||
|
|
||||||
// 自动加 %
|
|
||||||
for i := range args {
|
|
||||||
if s, ok := args[i].(string); ok {
|
|
||||||
args[i] = "%" + s + "%"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ORDER BY
|
where := ""
|
||||||
var order string
|
if searchCondition != "" {
|
||||||
|
where = " WHERE " + searchCondition
|
||||||
|
}
|
||||||
|
|
||||||
|
order := ""
|
||||||
if orderBy != "" {
|
if orderBy != "" {
|
||||||
order = " ORDER BY " + orderBy
|
order = " ORDER BY " + orderBy
|
||||||
}
|
}
|
||||||
|
|
||||||
sql := fmt.Sprintf("SELECT * FROM %s%s%s", viewName, where, order)
|
sql := fmt.Sprintf("SELECT %s FROM %s%s%s",
|
||||||
|
selectCols,
|
||||||
|
viewName,
|
||||||
|
where,
|
||||||
|
order,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.FromCtx(ctx).Info("LoadData",
|
||||||
|
zap.String("sql", sql),
|
||||||
|
zap.Any("args", args),
|
||||||
|
)
|
||||||
|
|
||||||
return c.query(ctx, sql, args...)
|
return c.query(ctx, sql, args...)
|
||||||
}
|
}
|
||||||
@@ -51,6 +53,10 @@ func (c *Client) LoadDataBySQL(
|
|||||||
sql string,
|
sql string,
|
||||||
args []any,
|
args []any,
|
||||||
) ([]map[string]any, error) {
|
) ([]map[string]any, error) {
|
||||||
|
logger.FromCtx(ctx).Info("LoadDataBySQL",
|
||||||
|
zap.String("sql", sql),
|
||||||
|
zap.Any("args", args),
|
||||||
|
)
|
||||||
return c.query(ctx, sql, args...)
|
return c.query(ctx, sql, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"allapp-go/internal/middleware"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) Update(
|
func (c *Client) Update(
|
||||||
@@ -18,6 +20,21 @@ func (c *Client) Update(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !auditExcludeTables[table] {
|
||||||
|
now := time.Now()
|
||||||
|
|
||||||
|
delete(data, "update_time")
|
||||||
|
delete(data, "update_by")
|
||||||
|
|
||||||
|
data["update_time"] = now
|
||||||
|
|
||||||
|
userID, flag := middleware.GetUserID(ctx)
|
||||||
|
|
||||||
|
if flag {
|
||||||
|
data["update_by"] = userID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
setClauses []string
|
setClauses []string
|
||||||
args []any
|
args []any
|
||||||
|
|||||||
@@ -46,10 +46,12 @@
|
|||||||
"react-dom": "19.2.0",
|
"react-dom": "19.2.0",
|
||||||
"react-native": "0.83.4",
|
"react-native": "0.83.4",
|
||||||
"react-native-gesture-handler": "~2.30.0",
|
"react-native-gesture-handler": "~2.30.0",
|
||||||
|
"react-native-keyboard-controller": "1.20.7",
|
||||||
"react-native-reanimated": "4.2.1",
|
"react-native-reanimated": "4.2.1",
|
||||||
"react-native-safe-area-context": "~5.6.2",
|
"react-native-safe-area-context": "~5.6.2",
|
||||||
"react-native-screens": "~4.23.0",
|
"react-native-screens": "~4.23.0",
|
||||||
"react-native-shadow-2": "^7.1.2",
|
"react-native-shadow-2": "^7.1.2",
|
||||||
|
"react-native-sortables": "^1.9.4",
|
||||||
"react-native-svg": "15.15.3",
|
"react-native-svg": "15.15.3",
|
||||||
"react-native-web": "~0.21.0",
|
"react-native-web": "~0.21.0",
|
||||||
"react-native-worklets": "0.7.2",
|
"react-native-worklets": "0.7.2",
|
||||||
|
|||||||
Generated
+86
-1
@@ -107,6 +107,9 @@ importers:
|
|||||||
react-native-gesture-handler:
|
react-native-gesture-handler:
|
||||||
specifier: ~2.30.0
|
specifier: ~2.30.0
|
||||||
version: 2.30.1(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
version: 2.30.1(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
||||||
|
react-native-keyboard-controller:
|
||||||
|
specifier: 1.20.7
|
||||||
|
version: 1.20.7(react-native-reanimated@4.2.1(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
||||||
react-native-reanimated:
|
react-native-reanimated:
|
||||||
specifier: 4.2.1
|
specifier: 4.2.1
|
||||||
version: 4.2.1(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
version: 4.2.1(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
||||||
@@ -119,6 +122,9 @@ importers:
|
|||||||
react-native-shadow-2:
|
react-native-shadow-2:
|
||||||
specifier: ^7.1.2
|
specifier: ^7.1.2
|
||||||
version: 7.1.2(react-native-svg@15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))
|
version: 7.1.2(react-native-svg@15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))
|
||||||
|
react-native-sortables:
|
||||||
|
specifier: ^1.9.4
|
||||||
|
version: 1.9.4(react-native-gesture-handler@2.30.1(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-reanimated@4.2.1(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
||||||
react-native-svg:
|
react-native-svg:
|
||||||
specifier: 15.15.3
|
specifier: 15.15.3
|
||||||
version: 15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
version: 15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
||||||
@@ -1004,48 +1010,56 @@ packages:
|
|||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxfmt/binding-linux-arm64-musl@0.44.0':
|
'@oxfmt/binding-linux-arm64-musl@0.44.0':
|
||||||
resolution: {integrity: sha512-AlObQIXyVRZ96LbtVljtFq0JqH5B92NU+BQeDFrXWBUWlCKAM0wF5GLfIhCLT5kQ3Sl+U0YjRJ7Alqj5hGQaCg==}
|
resolution: {integrity: sha512-AlObQIXyVRZ96LbtVljtFq0JqH5B92NU+BQeDFrXWBUWlCKAM0wF5GLfIhCLT5kQ3Sl+U0YjRJ7Alqj5hGQaCg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@oxfmt/binding-linux-ppc64-gnu@0.44.0':
|
'@oxfmt/binding-linux-ppc64-gnu@0.44.0':
|
||||||
resolution: {integrity: sha512-YcFE8/q/BbrCiIiM5piwbkA6GwJc5QqhMQp2yDrqQ2fuVkZ7CInb1aIijZ/k8EXc72qXMSwKpVlBv1w/MsGO/A==}
|
resolution: {integrity: sha512-YcFE8/q/BbrCiIiM5piwbkA6GwJc5QqhMQp2yDrqQ2fuVkZ7CInb1aIijZ/k8EXc72qXMSwKpVlBv1w/MsGO/A==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxfmt/binding-linux-riscv64-gnu@0.44.0':
|
'@oxfmt/binding-linux-riscv64-gnu@0.44.0':
|
||||||
resolution: {integrity: sha512-eOdzs6RqkRzuqNHUX5C8ISN5xfGh4xDww8OEd9YAmc3OWN8oAe5bmlIqQ+rrHLpv58/0BuU48bxkhnIGjA/ATQ==}
|
resolution: {integrity: sha512-eOdzs6RqkRzuqNHUX5C8ISN5xfGh4xDww8OEd9YAmc3OWN8oAe5bmlIqQ+rrHLpv58/0BuU48bxkhnIGjA/ATQ==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxfmt/binding-linux-riscv64-musl@0.44.0':
|
'@oxfmt/binding-linux-riscv64-musl@0.44.0':
|
||||||
resolution: {integrity: sha512-YBgNTxntD/QvlFUfgvh8bEdwOhXiquX8gaofZJAwYa/Xp1S1DQrFVZEeck7GFktr24DztsSp8N8WtWCBwxs0Hw==}
|
resolution: {integrity: sha512-YBgNTxntD/QvlFUfgvh8bEdwOhXiquX8gaofZJAwYa/Xp1S1DQrFVZEeck7GFktr24DztsSp8N8WtWCBwxs0Hw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@oxfmt/binding-linux-s390x-gnu@0.44.0':
|
'@oxfmt/binding-linux-s390x-gnu@0.44.0':
|
||||||
resolution: {integrity: sha512-GLIh1R6WHWshl/i4QQDNgj0WtT25aRO4HNUWEoitxiywyRdhTFmFEYT2rXlcl9U6/26vhmOqG5cRlMLG3ocaIA==}
|
resolution: {integrity: sha512-GLIh1R6WHWshl/i4QQDNgj0WtT25aRO4HNUWEoitxiywyRdhTFmFEYT2rXlcl9U6/26vhmOqG5cRlMLG3ocaIA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxfmt/binding-linux-x64-gnu@0.44.0':
|
'@oxfmt/binding-linux-x64-gnu@0.44.0':
|
||||||
resolution: {integrity: sha512-gZOpgTlOsLcLfAF9qgpTr7FIIFSKnQN3hDf/0JvQ4CIwMY7h+eilNjxq/CorqvYcEOu+LRt1W4ZS7KccEHLOdA==}
|
resolution: {integrity: sha512-gZOpgTlOsLcLfAF9qgpTr7FIIFSKnQN3hDf/0JvQ4CIwMY7h+eilNjxq/CorqvYcEOu+LRt1W4ZS7KccEHLOdA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxfmt/binding-linux-x64-musl@0.44.0':
|
'@oxfmt/binding-linux-x64-musl@0.44.0':
|
||||||
resolution: {integrity: sha512-1CyS9JTB+pCUFYFI6pkQGGZaT/AY5gnhHVrQQLhFba6idP9AzVYm1xbdWfywoldTYvjxQJV6x4SuduCIfP3W+A==}
|
resolution: {integrity: sha512-1CyS9JTB+pCUFYFI6pkQGGZaT/AY5gnhHVrQQLhFba6idP9AzVYm1xbdWfywoldTYvjxQJV6x4SuduCIfP3W+A==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@oxfmt/binding-openharmony-arm64@0.44.0':
|
'@oxfmt/binding-openharmony-arm64@0.44.0':
|
||||||
resolution: {integrity: sha512-bmEv70Ak6jLr1xotCbF5TxIKjsmQaiX+jFRtnGtfA03tJPf6VG3cKh96S21boAt3JZc+Vjx8PYcDuLj39vM2Pw==}
|
resolution: {integrity: sha512-bmEv70Ak6jLr1xotCbF5TxIKjsmQaiX+jFRtnGtfA03tJPf6VG3cKh96S21boAt3JZc+Vjx8PYcDuLj39vM2Pw==}
|
||||||
@@ -1118,48 +1132,56 @@ packages:
|
|||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxlint/binding-linux-arm64-musl@1.60.0':
|
'@oxlint/binding-linux-arm64-musl@1.60.0':
|
||||||
resolution: {integrity: sha512-eDYDXZGhQAXyn6GwtwiX/qcLS0HlOLPJ/+iiIY8RYr+3P8oKBmgKxADLlniL6FtWfE7pPk7IGN9/xvDEvDvFeg==}
|
resolution: {integrity: sha512-eDYDXZGhQAXyn6GwtwiX/qcLS0HlOLPJ/+iiIY8RYr+3P8oKBmgKxADLlniL6FtWfE7pPk7IGN9/xvDEvDvFeg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@oxlint/binding-linux-ppc64-gnu@1.60.0':
|
'@oxlint/binding-linux-ppc64-gnu@1.60.0':
|
||||||
resolution: {integrity: sha512-nxehly5XYBHUWI9VJX1bqCf9j/B43DaK/aS/T1fcxCpX3PA4Rm9BB54nPD1CKayT8xg6REN1ao+01hSRNgy8OA==}
|
resolution: {integrity: sha512-nxehly5XYBHUWI9VJX1bqCf9j/B43DaK/aS/T1fcxCpX3PA4Rm9BB54nPD1CKayT8xg6REN1ao+01hSRNgy8OA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxlint/binding-linux-riscv64-gnu@1.60.0':
|
'@oxlint/binding-linux-riscv64-gnu@1.60.0':
|
||||||
resolution: {integrity: sha512-j1qf/NaUfOWQutjeoooNG1Q0zsK0XGmSu1uDLq3cctquRF3j7t9Hxqf/76ehCc5GEUAanth2W4Fa+XT1RFg/nw==}
|
resolution: {integrity: sha512-j1qf/NaUfOWQutjeoooNG1Q0zsK0XGmSu1uDLq3cctquRF3j7t9Hxqf/76ehCc5GEUAanth2W4Fa+XT1RFg/nw==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxlint/binding-linux-riscv64-musl@1.60.0':
|
'@oxlint/binding-linux-riscv64-musl@1.60.0':
|
||||||
resolution: {integrity: sha512-YELKPRefQ/q/h3RUmeRfPCUhh2wBvgV1RyZ/F9M9u8cDyXsQW2ojv1DeWQTt466yczDITjZnIOg/s05pk7Ve2A==}
|
resolution: {integrity: sha512-YELKPRefQ/q/h3RUmeRfPCUhh2wBvgV1RyZ/F9M9u8cDyXsQW2ojv1DeWQTt466yczDITjZnIOg/s05pk7Ve2A==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@oxlint/binding-linux-s390x-gnu@1.60.0':
|
'@oxlint/binding-linux-s390x-gnu@1.60.0':
|
||||||
resolution: {integrity: sha512-JkO3C6Gki7Y6h/MiIkFKvHFOz98/YWvQ4WYbK9DLXACMP2rjULzkeGyAzorJE5S1dzLQGFgeqvN779kSFwoV1g==}
|
resolution: {integrity: sha512-JkO3C6Gki7Y6h/MiIkFKvHFOz98/YWvQ4WYbK9DLXACMP2rjULzkeGyAzorJE5S1dzLQGFgeqvN779kSFwoV1g==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxlint/binding-linux-x64-gnu@1.60.0':
|
'@oxlint/binding-linux-x64-gnu@1.60.0':
|
||||||
resolution: {integrity: sha512-XjKHdFVCpZZZSWBCKyyqCq65s2AKXykMXkjLoKYODrD+f5toLhlwsMESscu8FbgnJQ4Y/dpR/zdazsahmgBJIA==}
|
resolution: {integrity: sha512-XjKHdFVCpZZZSWBCKyyqCq65s2AKXykMXkjLoKYODrD+f5toLhlwsMESscu8FbgnJQ4Y/dpR/zdazsahmgBJIA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@oxlint/binding-linux-x64-musl@1.60.0':
|
'@oxlint/binding-linux-x64-musl@1.60.0':
|
||||||
resolution: {integrity: sha512-js29ZWIuPhNWzY8NC7KoffEMEeWG105vbmm+8EOJsC+T/jHBiKIJEUF78+F/IrgEWMMP9N0kRND4Pp75+xAhKg==}
|
resolution: {integrity: sha512-js29ZWIuPhNWzY8NC7KoffEMEeWG105vbmm+8EOJsC+T/jHBiKIJEUF78+F/IrgEWMMP9N0kRND4Pp75+xAhKg==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@oxlint/binding-openharmony-arm64@1.60.0':
|
'@oxlint/binding-openharmony-arm64@1.60.0':
|
||||||
resolution: {integrity: sha512-H+PUITKHk04stFpWj3x3Kg08Afp/bcXSBi0EhasR5a0Vw7StXHTzdl655PUI0fB4qdh2Wsu6Dsi+3ACxPoyQnA==}
|
resolution: {integrity: sha512-H+PUITKHk04stFpWj3x3Kg08Afp/bcXSBi0EhasR5a0Vw7StXHTzdl655PUI0fB4qdh2Wsu6Dsi+3ACxPoyQnA==}
|
||||||
@@ -1578,24 +1600,28 @@ packages:
|
|||||||
engines: {node: '>= 20'}
|
engines: {node: '>= 20'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-arm64-musl@4.2.1':
|
'@tailwindcss/oxide-linux-arm64-musl@4.2.1':
|
||||||
resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==}
|
resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==}
|
||||||
engines: {node: '>= 20'}
|
engines: {node: '>= 20'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-x64-gnu@4.2.1':
|
'@tailwindcss/oxide-linux-x64-gnu@4.2.1':
|
||||||
resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==}
|
resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==}
|
||||||
engines: {node: '>= 20'}
|
engines: {node: '>= 20'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-x64-musl@4.2.1':
|
'@tailwindcss/oxide-linux-x64-musl@4.2.1':
|
||||||
resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==}
|
resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==}
|
||||||
engines: {node: '>= 20'}
|
engines: {node: '>= 20'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@tailwindcss/oxide-wasm32-wasi@4.2.1':
|
'@tailwindcss/oxide-wasm32-wasi@4.2.1':
|
||||||
resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==}
|
resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==}
|
||||||
@@ -2513,7 +2539,7 @@ packages:
|
|||||||
|
|
||||||
glob@7.2.3:
|
glob@7.2.3:
|
||||||
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
|
||||||
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
deprecated: Glob versions prior to v9 are no longer supported
|
||||||
|
|
||||||
gopd@1.2.0:
|
gopd@1.2.0:
|
||||||
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
@@ -2846,72 +2872,84 @@ packages:
|
|||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-arm64-gnu@1.31.1:
|
lightningcss-linux-arm64-gnu@1.31.1:
|
||||||
resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==}
|
resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-arm64-gnu@1.32.0:
|
lightningcss-linux-arm64-gnu@1.32.0:
|
||||||
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
|
resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.30.1:
|
lightningcss-linux-arm64-musl@1.30.1:
|
||||||
resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
|
resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.31.1:
|
lightningcss-linux-arm64-musl@1.31.1:
|
||||||
resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==}
|
resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.32.0:
|
lightningcss-linux-arm64-musl@1.32.0:
|
||||||
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.30.1:
|
lightningcss-linux-x64-gnu@1.30.1:
|
||||||
resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
|
resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.31.1:
|
lightningcss-linux-x64-gnu@1.31.1:
|
||||||
resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==}
|
resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.32.0:
|
lightningcss-linux-x64-gnu@1.32.0:
|
||||||
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.30.1:
|
lightningcss-linux-x64-musl@1.30.1:
|
||||||
resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
|
resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.31.1:
|
lightningcss-linux-x64-musl@1.31.1:
|
||||||
resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==}
|
resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.32.0:
|
lightningcss-linux-x64-musl@1.32.0:
|
||||||
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-win32-arm64-msvc@1.30.1:
|
lightningcss-win32-arm64-msvc@1.30.1:
|
||||||
resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
|
resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==}
|
||||||
@@ -3454,6 +3492,15 @@ packages:
|
|||||||
react: '*'
|
react: '*'
|
||||||
react-native: '*'
|
react-native: '*'
|
||||||
|
|
||||||
|
react-native-haptic-feedback@3.0.0:
|
||||||
|
resolution: {integrity: sha512-yxBQFHhXU8S0xupGtzDRToGw5yMWezUdLzxtlx2Oi9YpjPwprWZWCw8foPt0S2t84B0ZikzZYyOHiMSXIelUxg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=18.0.0'
|
||||||
|
react-native: '>=0.71.0'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
|
||||||
react-native-iconfont-cli@2.2.4:
|
react-native-iconfont-cli@2.2.4:
|
||||||
resolution: {integrity: sha512-qTjfHG2f/sc3/YKWZJvIJk/SwiTu8C9/SvtoLxRX+GM/6pvOJq0ge6rcV3vTh4s3QJDuNDOD6+a4hGD6vv2vSg==}
|
resolution: {integrity: sha512-qTjfHG2f/sc3/YKWZJvIJk/SwiTu8C9/SvtoLxRX+GM/6pvOJq0ge6rcV3vTh4s3QJDuNDOD6+a4hGD6vv2vSg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -3474,6 +3521,13 @@ packages:
|
|||||||
react: '*'
|
react: '*'
|
||||||
react-native: '*'
|
react-native: '*'
|
||||||
|
|
||||||
|
react-native-keyboard-controller@1.20.7:
|
||||||
|
resolution: {integrity: sha512-G8S5jz1FufPrcL1vPtReATx+jJhT/j+sTqxMIb30b1z7cYEfMlkIzOCyaHgf6IMB2KA9uBmnA5M6ve2A9Ou4kw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '*'
|
||||||
|
react-native: '*'
|
||||||
|
react-native-reanimated: '>=3.0.0'
|
||||||
|
|
||||||
react-native-reanimated@4.2.1:
|
react-native-reanimated@4.2.1:
|
||||||
resolution: {integrity: sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg==}
|
resolution: {integrity: sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -3499,6 +3553,14 @@ packages:
|
|||||||
react-native: '*'
|
react-native: '*'
|
||||||
react-native-svg: '>=12.1.0'
|
react-native-svg: '>=12.1.0'
|
||||||
|
|
||||||
|
react-native-sortables@1.9.4:
|
||||||
|
resolution: {integrity: sha512-a6hxT+gl14HA5Sm8UiLXJqF8KMEQVa+mUJd75OnzoVsmrxUDtjAatlMdV0kI9qTQDT/ZSFLPRmdUhOR762IA4g==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '*'
|
||||||
|
react-native: '*'
|
||||||
|
react-native-gesture-handler: '>=2.0.0'
|
||||||
|
react-native-reanimated: '>=3.0.0'
|
||||||
|
|
||||||
react-native-svg@15.15.3:
|
react-native-svg@15.15.3:
|
||||||
resolution: {integrity: sha512-/k4KYwPBLGcx2f5d4FjE+vCScK7QOX14cl2lIASJ28u4slHHtIhL0SZKU7u9qmRBHxTCKPoPBtN6haT1NENJNA==}
|
resolution: {integrity: sha512-/k4KYwPBLGcx2f5d4FjE+vCScK7QOX14cl2lIASJ28u4slHHtIhL0SZKU7u9qmRBHxTCKPoPBtN6haT1NENJNA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -7868,6 +7930,13 @@ snapshots:
|
|||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
|
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
|
||||||
|
|
||||||
|
react-native-haptic-feedback@3.0.0(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
|
||||||
|
optionalDependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
optional: true
|
||||||
|
|
||||||
react-native-iconfont-cli@2.2.4(react-native-svg@15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0):
|
react-native-iconfont-cli@2.2.4(react-native-svg@15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
colors: 1.4.0
|
colors: 1.4.0
|
||||||
@@ -7893,6 +7962,13 @@ snapshots:
|
|||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
|
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
|
||||||
|
|
||||||
|
react-native-keyboard-controller@1.20.7(react-native-reanimated@4.2.1(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
|
||||||
|
react-native-is-edge-to-edge: 1.3.1(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
||||||
|
react-native-reanimated: 4.2.1(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
||||||
|
|
||||||
react-native-reanimated@4.2.1(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0):
|
react-native-reanimated@4.2.1(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.0
|
react: 19.2.0
|
||||||
@@ -7919,6 +7995,15 @@ snapshots:
|
|||||||
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
|
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
|
||||||
react-native-svg: 15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
react-native-svg: 15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
||||||
|
|
||||||
|
react-native-sortables@1.9.4(react-native-gesture-handler@2.30.1(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-reanimated@4.2.1(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0):
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.0
|
||||||
|
react-native: 0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)
|
||||||
|
react-native-gesture-handler: 2.30.1(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
||||||
|
react-native-reanimated: 4.2.1(react-native-worklets@0.7.2(@babel/core@7.29.0)(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
||||||
|
optionalDependencies:
|
||||||
|
react-native-haptic-feedback: 3.0.0(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)
|
||||||
|
|
||||||
react-native-svg@15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0):
|
react-native-svg@15.15.3(react-native@0.83.4(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
css-select: 5.2.2
|
css-select: 5.2.2
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ const tabs: TabbarItem[] = [
|
|||||||
icon: HomeIcon,
|
icon: HomeIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "/chat",
|
id: "/space",
|
||||||
title: "聊天",
|
title: "空间",
|
||||||
icon: MessageCircleIcon,
|
icon: EarthIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "/tools",
|
id: "/tools",
|
||||||
@@ -31,9 +31,9 @@ const tabs: TabbarItem[] = [
|
|||||||
isCenter: true,
|
isCenter: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "/space",
|
id: "/chat",
|
||||||
title: "空间",
|
title: "聊天",
|
||||||
icon: EarthIcon,
|
icon: MessageCircleIcon,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "/my",
|
id: "/my",
|
||||||
|
|||||||
@@ -1,36 +1,43 @@
|
|||||||
import { Button, Dialog } from "heroui-native";
|
import { Button, Toast, useToast, useThemeColor } from "heroui-native";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export default function DialogExample() {
|
export default function ToastExample() {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const { toast } = useToast();
|
||||||
|
const themeColorForeground = useThemeColor("foreground");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<View className="gap-4 p-4">
|
||||||
<Button variant="primary" onPress={() => {
|
<Button
|
||||||
setIsOpen(true)
|
onPress={() =>
|
||||||
}}>Open Dialog</Button>
|
toast.show({
|
||||||
|
variant: "success",
|
||||||
|
label: "You have upgraded your plan",
|
||||||
|
description: "You can continue using HeroUI Chat",
|
||||||
|
actionLabel: "Close",
|
||||||
|
onActionPress: ({ hide }) => hide(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
>
|
||||||
|
Show Success Toast
|
||||||
|
</Button>
|
||||||
|
|
||||||
<Dialog isOpen={isOpen} onOpenChange={setIsOpen}>
|
<Button
|
||||||
<Dialog.Portal>
|
onPress={() =>
|
||||||
<Dialog.Overlay />
|
toast.show({
|
||||||
<Dialog.Content>
|
duration: 0,
|
||||||
<Dialog.Close variant="ghost" />
|
component: (props) => (
|
||||||
<View className="mb-5 gap-1.5">
|
<Toast variant="accent" {...props}>
|
||||||
<Dialog.Title>Confirm Action</Dialog.Title>
|
<Toast.Title>Custom Toast</Toast.Title>
|
||||||
<Dialog.Description>
|
<Toast.Description>This uses a custom component</Toast.Description>
|
||||||
Are you sure you want to proceed with this action? This cannot be undone.
|
<Toast.Action onPress={() => props.hide()}>Undo</Toast.Action>
|
||||||
</Dialog.Description>
|
<Toast.Close className="absolute top-0 right-0" />
|
||||||
</View>
|
</Toast>
|
||||||
<View className="flex-row justify-end gap-3">
|
),
|
||||||
<Button variant="ghost" size="sm" onPress={() => setIsOpen(false)}>
|
})
|
||||||
Cancel
|
}
|
||||||
</Button>
|
>
|
||||||
<Button size="sm">Confirm</Button>
|
Show Custom Toast
|
||||||
</View>
|
</Button>
|
||||||
</Dialog.Content>
|
</View>
|
||||||
</Dialog.Portal>
|
|
||||||
</Dialog>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,566 @@
|
|||||||
import React from "react";
|
import React, { useEffect, useState, useRef } from "react";
|
||||||
import LayoutView from "@/components/layout/view";
|
import LayoutView from "@/components/layout/view";
|
||||||
import { Text } from "react-native";
|
import { View, Text } from "react-native";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Separator,
|
||||||
|
Spinner,
|
||||||
|
Chip,
|
||||||
|
BottomSheet,
|
||||||
|
Label,
|
||||||
|
TextField,
|
||||||
|
Input,
|
||||||
|
InputOTP,
|
||||||
|
type InputOTPRef,
|
||||||
|
PressableFeedback,
|
||||||
|
useBottomSheetAwareHandlers,
|
||||||
|
} from "heroui-native";
|
||||||
|
import { PlusIcon, HousePlusIcon, GripIcon } from "lucide-react-native";
|
||||||
|
import { useUserStore } from "@/store";
|
||||||
|
import { getUniqueIdApi, loadDataApi, loadDataBySqlApi, saveDataApi } from "@/request/api";
|
||||||
|
import EmptyState from "@/components/EmptyStatus";
|
||||||
|
import Sortable from "react-native-sortables";
|
||||||
|
import { useSpaceStore } from "@/store";
|
||||||
|
import { useRouter } from "expo-router";
|
||||||
|
import { UserRoundIcon, UsersRoundIcon } from "lucide-react-native";
|
||||||
|
import { toast } from "sonner-native";
|
||||||
|
import { FadeIn } from "react-native-reanimated";
|
||||||
|
import { generateCode } from "@/utils/common";
|
||||||
|
|
||||||
export default function Space() {
|
export default function Space() {
|
||||||
|
const router = useRouter();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const spaceStore = useSpaceStore();
|
||||||
|
|
||||||
|
const ITEM_HEIGHT = 64;
|
||||||
|
|
||||||
|
const [spaces, setSpaces] = useState<any[]>([]);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
// 加入
|
||||||
|
const [showJoin, setShowJoin] = useState(false);
|
||||||
|
const [joinLoading, setJoinLoading] = useState(false);
|
||||||
|
const [joinForm, setJoinForm] = useState({
|
||||||
|
invite_code: "",
|
||||||
|
});
|
||||||
|
const joinInputRef = useRef<InputOTPRef>(null);
|
||||||
|
|
||||||
|
// 新建
|
||||||
|
const [showCreate, setShowCreate] = useState(false);
|
||||||
|
const [createForm, setCreateForm] = useState({
|
||||||
|
name: "",
|
||||||
|
});
|
||||||
|
const [createLoading, setCreateLoading] = useState(false);
|
||||||
|
const createInputRef = useRef<any>(null);
|
||||||
|
|
||||||
|
const { onFocus, onBlur } = useBottomSheetAwareHandlers();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showCreate) {
|
||||||
|
setTimeout(() => {
|
||||||
|
createInputRef.current?.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}, [showCreate]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (showJoin) {
|
||||||
|
setTimeout(() => {
|
||||||
|
joinInputRef.current?.focus();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}, [showJoin]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
listUserSpaces();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function listUserSpaces(loading = true) {
|
||||||
|
if (loading) {
|
||||||
|
setLoading(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDataApi({
|
||||||
|
view_name: "v_user_spaces",
|
||||||
|
search_condition: `v_user_id = ?`,
|
||||||
|
search_columns: [],
|
||||||
|
order_by: "",
|
||||||
|
args: [userStore.userInfo.id],
|
||||||
|
}).then((res) => {
|
||||||
|
if (res.isSuccess) {
|
||||||
|
setSpaces(res.data);
|
||||||
|
if (loading) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCreate() {
|
||||||
|
if (createLoading) return;
|
||||||
|
|
||||||
|
if (createForm.name === "") {
|
||||||
|
toast.warning("空间名称不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setCreateLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
let inviteCode = "";
|
||||||
|
let isExist = true;
|
||||||
|
|
||||||
|
// 循环生成直到不存在
|
||||||
|
while (isExist) {
|
||||||
|
inviteCode = generateCode(6);
|
||||||
|
|
||||||
|
const res = await loadDataBySqlApi({
|
||||||
|
sql: `SELECT 1 FROM space WHERE invite_code = ? LIMIT 1`,
|
||||||
|
args: [inviteCode],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.isSuccess) {
|
||||||
|
throw new Error("校验邀请码失败");
|
||||||
|
}
|
||||||
|
|
||||||
|
isExist = res.data.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置是否是默认空间
|
||||||
|
let isDefault = false;
|
||||||
|
|
||||||
|
const defaultRes = await loadDataApi({
|
||||||
|
view_name: "v_user_spaces",
|
||||||
|
search_condition: `v_user_id = ?`,
|
||||||
|
search_columns: ["v_is_default"],
|
||||||
|
order_by: "",
|
||||||
|
args: [userStore.userInfo.id],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!defaultRes.isSuccess) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultRes.data.length == 0) {
|
||||||
|
isDefault = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建
|
||||||
|
const autoRes = await getUniqueIdApi(2);
|
||||||
|
if (!autoRes.isSuccess) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const spaceId = autoRes.data[0];
|
||||||
|
const spaceMemberId = autoRes.data[1];
|
||||||
|
|
||||||
|
// 插入空间
|
||||||
|
const requestData = [];
|
||||||
|
|
||||||
|
requestData.push({
|
||||||
|
table_name: "space",
|
||||||
|
key_field: "id",
|
||||||
|
inserts: [
|
||||||
|
{
|
||||||
|
id: spaceId,
|
||||||
|
name: createForm.name,
|
||||||
|
invite_code: inviteCode,
|
||||||
|
owner_id: userStore.userInfo.id,
|
||||||
|
status: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
updates: [],
|
||||||
|
deletes: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
// 插入空间用户
|
||||||
|
requestData.push({
|
||||||
|
table_name: "space_member",
|
||||||
|
key_field: "id",
|
||||||
|
inserts: [
|
||||||
|
{
|
||||||
|
id: spaceMemberId,
|
||||||
|
space_id: spaceId,
|
||||||
|
user_id: userStore.userInfo.id,
|
||||||
|
role: 0,
|
||||||
|
is_default: isDefault ? 1 : 0,
|
||||||
|
sort_number: defaultRes.data.length,
|
||||||
|
member_name: userStore.userInfo.nickname,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
updates: [],
|
||||||
|
deletes: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const saveRes = await saveDataApi(requestData);
|
||||||
|
|
||||||
|
if (saveRes.isSuccess) {
|
||||||
|
toast.success("创建成功");
|
||||||
|
setShowCreate(false);
|
||||||
|
|
||||||
|
setCreateForm({
|
||||||
|
name: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
listUserSpaces();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
} finally {
|
||||||
|
setCreateLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleJoin() {
|
||||||
|
if (joinLoading) return;
|
||||||
|
|
||||||
|
const code = joinForm.invite_code.trim();
|
||||||
|
|
||||||
|
if (!code) {
|
||||||
|
toast.warning("邀请码不能为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^[A-Za-z0-9]{6}$/.test(code)) {
|
||||||
|
toast.warning("邀请码必须为6位字母或数字");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setJoinLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await loadDataBySqlApi({
|
||||||
|
sql: ` SELECT
|
||||||
|
s.id,
|
||||||
|
s.name,
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM space_member sm
|
||||||
|
WHERE sm.space_id = s.id
|
||||||
|
AND sm.user_id = ?
|
||||||
|
) AS is_joined
|
||||||
|
FROM space s
|
||||||
|
WHERE s.invite_code = ?
|
||||||
|
LIMIT 1;
|
||||||
|
`,
|
||||||
|
args: [userStore.userInfo.id, code],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.isSuccess) return;
|
||||||
|
|
||||||
|
const row = res.data?.[0];
|
||||||
|
|
||||||
|
if (!row) {
|
||||||
|
toast.warning("邀请码无效");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (row.is_joined) {
|
||||||
|
toast.warning("你已经在该空间了");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const autoRes = await getUniqueIdApi(1);
|
||||||
|
if (!autoRes.isSuccess) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置是否是默认空间
|
||||||
|
let isDefault = false;
|
||||||
|
|
||||||
|
const defaultRes = await loadDataApi({
|
||||||
|
view_name: "v_user_spaces",
|
||||||
|
search_condition: `v_user_id = ?`,
|
||||||
|
search_columns: ["v_is_default"],
|
||||||
|
order_by: "",
|
||||||
|
args: [userStore.userInfo.id],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!defaultRes.isSuccess) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultRes.data.length == 0) {
|
||||||
|
isDefault = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加入
|
||||||
|
const requestData = [];
|
||||||
|
|
||||||
|
requestData.push({
|
||||||
|
table_name: "space_member",
|
||||||
|
key_field: "id",
|
||||||
|
inserts: [
|
||||||
|
{
|
||||||
|
id: autoRes.data[0],
|
||||||
|
space_id: row.id,
|
||||||
|
user_id: userStore.userInfo.id,
|
||||||
|
role: 1,
|
||||||
|
is_default: isDefault ? 1 : 0,
|
||||||
|
sort_number: defaultRes.data.length,
|
||||||
|
member_name: userStore.userInfo.nickname,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
updates: [],
|
||||||
|
deletes: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const saveRes = await saveDataApi(requestData);
|
||||||
|
|
||||||
|
if (saveRes.isSuccess) {
|
||||||
|
toast.success("加入成功");
|
||||||
|
setShowJoin(false);
|
||||||
|
|
||||||
|
setJoinForm({
|
||||||
|
invite_code: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
listUserSpaces();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
toast.error("加入失败,请稍后重试");
|
||||||
|
} finally {
|
||||||
|
setJoinLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderItem = ({ item, index }: { item: any; index: number }) => (
|
||||||
|
<PressableFeedback
|
||||||
|
key={item.space_id}
|
||||||
|
onPress={() => {
|
||||||
|
router.navigate({
|
||||||
|
pathname: "/tab-space" as any,
|
||||||
|
params: {
|
||||||
|
spaceId: item.space_id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
height: ITEM_HEIGHT,
|
||||||
|
}}
|
||||||
|
className={`rounded-xl flex-row items-center px-4 py-3 bg-white ${
|
||||||
|
index === spaces.length - 1 ? "mb-4" : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<View className="w-full flex-row items-center justify-between">
|
||||||
|
<View>
|
||||||
|
<View className="flex-row items-center gap-2">
|
||||||
|
<Text className="font-semibold text-gray-900">{item.name}</Text>
|
||||||
|
|
||||||
|
{item.owner_id === userStore.userInfo.id && (
|
||||||
|
<Chip size="sm" variant="soft" color="success">
|
||||||
|
创建者
|
||||||
|
</Chip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item.v_is_default === 1 && (
|
||||||
|
<Chip size="sm" variant="secondary" color="default">
|
||||||
|
默认
|
||||||
|
</Chip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item.space_id === spaceStore?.currentSpace?.space_id && (
|
||||||
|
<Chip size="sm" variant="primary" color="accent">
|
||||||
|
当前空间
|
||||||
|
</Chip>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="flex-row items-center gap-3 mt-3">
|
||||||
|
<View className="flex-row gap-1 items-center">
|
||||||
|
<UserRoundIcon size={12} />
|
||||||
|
<Text className="text-xs">{item.v_member_name}</Text>
|
||||||
|
</View>
|
||||||
|
<View className="flex-row gap-1 items-center">
|
||||||
|
<UsersRoundIcon size={12} />
|
||||||
|
<Text className={`text-xs `}>{item.v_member_count}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Sortable.Handle>
|
||||||
|
<GripIcon size={20} />
|
||||||
|
</Sortable.Handle>
|
||||||
|
</View>
|
||||||
|
<PressableFeedback.Ripple />
|
||||||
|
</PressableFeedback>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LayoutView>
|
<LayoutView>
|
||||||
<LayoutView.ScrollView>
|
<LayoutView.ScrollView>
|
||||||
<Text className="text-base text-black">我的空间</Text>
|
<View className="flex-row gap-4">
|
||||||
|
<Button
|
||||||
|
className="flex-1"
|
||||||
|
onPress={() => {
|
||||||
|
setShowCreate(true);
|
||||||
|
createInputRef.current?.focus();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusIcon color="#fff" size={20} />
|
||||||
|
<Button.Label>创建新空间</Button.Label>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="flex-1"
|
||||||
|
variant="outline"
|
||||||
|
onPress={() => {
|
||||||
|
setShowJoin(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<HousePlusIcon size={20} />
|
||||||
|
<Button.Label>加入其它空间</Button.Label>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="flex-row my-3 items-center gap-3">
|
||||||
|
<Separator className="flex-1" />
|
||||||
|
<Text className="text-xs text-gray-400">我的空间</Text>
|
||||||
|
<Separator className="flex-1" />
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{loading ? (
|
||||||
|
<View className="flex-1 items-center justify-center">
|
||||||
|
<Spinner size="lg" />
|
||||||
|
</View>
|
||||||
|
) : spaces.length === 0 ? (
|
||||||
|
<EmptyState />
|
||||||
|
) : (
|
||||||
|
<Sortable.Grid
|
||||||
|
keyExtractor={(item: any) => {
|
||||||
|
return String(item.space_id);
|
||||||
|
}}
|
||||||
|
activeItemScale={1}
|
||||||
|
columns={1}
|
||||||
|
data={spaces}
|
||||||
|
overDrag="vertical"
|
||||||
|
renderItem={renderItem}
|
||||||
|
rowGap={10}
|
||||||
|
highlight-next-line
|
||||||
|
customHandle
|
||||||
|
autoScrollActivationOffset={200}
|
||||||
|
onDragEnd={(dragData: any) => {
|
||||||
|
const list = dragData.data;
|
||||||
|
|
||||||
|
const updates = list.map((item: any, index: number) => ({
|
||||||
|
id: item.v_space_member_id,
|
||||||
|
sort_number: index,
|
||||||
|
}));
|
||||||
|
|
||||||
|
saveDataApi([
|
||||||
|
{
|
||||||
|
table_name: "space_member",
|
||||||
|
key_field: "id",
|
||||||
|
inserts: [],
|
||||||
|
updates,
|
||||||
|
deletes: [],
|
||||||
|
},
|
||||||
|
]).then((_res) => {
|
||||||
|
// console.log(res);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</LayoutView.ScrollView>
|
</LayoutView.ScrollView>
|
||||||
|
|
||||||
|
<BottomSheet
|
||||||
|
isOpen={showCreate}
|
||||||
|
onOpenChange={(val) => {
|
||||||
|
if (!val) {
|
||||||
|
setCreateForm({
|
||||||
|
name: "",
|
||||||
|
});
|
||||||
|
setCreateLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowCreate(val);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BottomSheet.Portal>
|
||||||
|
<BottomSheet.Overlay />
|
||||||
|
|
||||||
|
<BottomSheet.Content>
|
||||||
|
<View>
|
||||||
|
<TextField>
|
||||||
|
<Label>空间名称</Label>
|
||||||
|
<Input
|
||||||
|
ref={createInputRef}
|
||||||
|
placeholder="请输入空间名称"
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
|
value={createForm.name}
|
||||||
|
onChangeText={(val) =>
|
||||||
|
setCreateForm({
|
||||||
|
name: val,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<Button className="mt-3" onPress={handleCreate}>
|
||||||
|
{createLoading && <Spinner entering={FadeIn.delay(50)} color="white" />}
|
||||||
|
<Button.Label>创建</Button.Label>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</BottomSheet.Content>
|
||||||
|
</BottomSheet.Portal>
|
||||||
|
</BottomSheet>
|
||||||
|
|
||||||
|
<BottomSheet
|
||||||
|
isOpen={showJoin}
|
||||||
|
onOpenChange={(val) => {
|
||||||
|
if (!val) {
|
||||||
|
setJoinForm({
|
||||||
|
invite_code: "",
|
||||||
|
});
|
||||||
|
setJoinLoading(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowJoin(val);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BottomSheet.Portal>
|
||||||
|
<BottomSheet.Overlay />
|
||||||
|
|
||||||
|
<BottomSheet.Content>
|
||||||
|
<View>
|
||||||
|
<TextField>
|
||||||
|
<Label>邀请码</Label>
|
||||||
|
<InputOTP
|
||||||
|
ref={joinInputRef}
|
||||||
|
maxLength={6}
|
||||||
|
onFocus={onFocus}
|
||||||
|
onBlur={onBlur}
|
||||||
|
value={joinForm.invite_code}
|
||||||
|
onChange={(val) => {
|
||||||
|
setJoinForm({
|
||||||
|
invite_code: val,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<InputOTP.Group>
|
||||||
|
<InputOTP.Slot index={0} />
|
||||||
|
<InputOTP.Slot index={1} />
|
||||||
|
<InputOTP.Slot index={2} />
|
||||||
|
</InputOTP.Group>
|
||||||
|
<InputOTP.Separator />
|
||||||
|
<InputOTP.Group>
|
||||||
|
<InputOTP.Slot index={3} />
|
||||||
|
<InputOTP.Slot index={4} />
|
||||||
|
<InputOTP.Slot index={5} />
|
||||||
|
</InputOTP.Group>
|
||||||
|
</InputOTP>
|
||||||
|
</TextField>
|
||||||
|
|
||||||
|
<Button className="mt-3" onPress={handleJoin}>
|
||||||
|
{joinLoading && <Spinner entering={FadeIn.delay(50)} color="white" />}
|
||||||
|
<Button.Label>加入</Button.Label>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</BottomSheet.Content>
|
||||||
|
</BottomSheet.Portal>
|
||||||
|
</BottomSheet>
|
||||||
</LayoutView>
|
</LayoutView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -18,11 +18,11 @@ export default function NotFound() {
|
|||||||
className="mt-4"
|
className="mt-4"
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
router.replace("/home");
|
router.back();
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
返回首页
|
返回
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,10 +8,15 @@ import "@/global.css";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Stack } from "expo-router";
|
import { Stack } from "expo-router";
|
||||||
import { Toaster } from "sonner-native";
|
import { Toaster } from "sonner-native";
|
||||||
|
import { KeyboardProvider } from "react-native-keyboard-controller";
|
||||||
|
import { useUserStore } from "@/store";
|
||||||
|
import { useRouter } from "expo-router";
|
||||||
|
|
||||||
SplashScreen.preventAutoHideAsync();
|
SplashScreen.preventAutoHideAsync();
|
||||||
|
|
||||||
export default function RootLayout() {
|
export default function RootLayout() {
|
||||||
|
const router = useRouter();
|
||||||
|
const userStore = useUserStore();
|
||||||
const isDark = useIsDark();
|
const isDark = useIsDark();
|
||||||
const isDev = __DEV__;
|
const isDev = __DEV__;
|
||||||
|
|
||||||
@@ -29,31 +34,40 @@ export default function RootLayout() {
|
|||||||
init();
|
init();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (userStore.authExpired) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
router.replace("/auth/login");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [userStore.authExpired]);
|
||||||
|
|
||||||
if (!isReady) return null;
|
if (!isReady) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GestureHandlerRootView className="flex-1">
|
<GestureHandlerRootView className="flex-1">
|
||||||
<ThemeProvider value={isDark ? DarkTheme : DefaultTheme}>
|
<KeyboardProvider>
|
||||||
<HeroUINativeProvider
|
<ThemeProvider value={isDark ? DarkTheme : DefaultTheme}>
|
||||||
config={{
|
<HeroUINativeProvider
|
||||||
devInfo: {
|
config={{
|
||||||
stylingPrinciples: false,
|
devInfo: {
|
||||||
},
|
stylingPrinciples: false,
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Stack
|
|
||||||
screenOptions={{
|
|
||||||
animation: "slide_from_right",
|
|
||||||
contentStyle: {
|
|
||||||
backgroundColor: "#fff",
|
|
||||||
},
|
},
|
||||||
headerShown: false,
|
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
{!isDev && <StatusBar style={isDark ? "light" : "dark"} />}
|
<Stack
|
||||||
</HeroUINativeProvider>
|
screenOptions={{
|
||||||
</ThemeProvider>
|
animation: "slide_from_right",
|
||||||
|
contentStyle: {
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
},
|
||||||
|
headerShown: false,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{!isDev && <StatusBar style={isDark ? "light" : "dark"} />}
|
||||||
|
</HeroUINativeProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</KeyboardProvider>
|
||||||
<Toaster duration={1000} />
|
<Toaster duration={1000} />
|
||||||
</GestureHandlerRootView>
|
</GestureHandlerRootView>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,12 +33,14 @@ export default function Login() {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
userStore.setUserInfo({
|
userStore.setUserInfo({
|
||||||
id: 1,
|
id: 779895419121733,
|
||||||
token: "123",
|
token: "123",
|
||||||
nickname: "Zhang",
|
nickname: "Zhang",
|
||||||
avatar: "",
|
avatar: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
userStore.setAuthExpired(false);
|
||||||
|
|
||||||
router.replace("/(tabs)/home");
|
router.replace("/(tabs)/home");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import React from "react";
|
||||||
|
import LayoutView from "@/components/layout/view";
|
||||||
|
import { View, Text } from "react-native";
|
||||||
|
import Navbar from "@/components/layout/navbar";
|
||||||
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
|
import { EarthIcon } from "lucide-react-native";
|
||||||
|
import { useRouter } from "expo-router";
|
||||||
|
|
||||||
|
export default function Space() {
|
||||||
|
const insets = useSafeAreaInsets();
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<View style={{ flex: 1, zIndex: 0 }}>
|
||||||
|
<LayoutView>
|
||||||
|
<LayoutView.ScrollView>
|
||||||
|
<Text className="text-base text-black">Space</Text>
|
||||||
|
</LayoutView.ScrollView>
|
||||||
|
</LayoutView>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
zIndex: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<View style={{ paddingTop: insets.top }}>
|
||||||
|
<Navbar
|
||||||
|
title={"空间"}
|
||||||
|
titleIcon={EarthIcon}
|
||||||
|
onBack={() => {
|
||||||
|
router.back();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,6 +9,12 @@ export function loginWxApi(params: LoginWxParams) {
|
|||||||
return alovaInstance.Post<ResponseData<LoginResult>>(`/auth/login/wechat`, params);
|
return alovaInstance.Post<ResponseData<LoginResult>>(`/auth/login/wechat`, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getUniqueIdApi(count = 1) {
|
||||||
|
return alovaInstance.Post<ResponseData<number[]>>(`/data/getUniqueId`, {
|
||||||
|
count: count,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
interface LoadDataBySqlParams {
|
interface LoadDataBySqlParams {
|
||||||
sql: string;
|
sql: string;
|
||||||
args: any[];
|
args: any[];
|
||||||
@@ -23,10 +29,21 @@ interface LoadDataParams {
|
|||||||
search_condition: string;
|
search_condition: string;
|
||||||
order_by: string;
|
order_by: string;
|
||||||
search_columns: string[];
|
search_columns: string[];
|
||||||
args: string[];
|
args: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function loadDataApi(params: LoadDataParams) {
|
export function loadDataApi(params: LoadDataParams) {
|
||||||
return alovaInstance.Post<ResponseData<any[]>>(`/data/loadData`, params);
|
return alovaInstance.Post<ResponseData<any[]>>(`/data/loadData`, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SaveDataParams {
|
||||||
|
table_name: string;
|
||||||
|
key_field: string;
|
||||||
|
inserts: any[];
|
||||||
|
updates: any[];
|
||||||
|
deletes: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveDataApi(params: SaveDataParams[]) {
|
||||||
|
return alovaInstance.Post<ResponseData<null>>(`/data/saveData`, params);
|
||||||
|
}
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
export * from "./app";
|
export * from "./app";
|
||||||
export * from "./user";
|
export * from "./user";
|
||||||
|
export * from "./space";
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
|
import { create } from "zustand";
|
||||||
|
import { createJSONStorage, persist } from "zustand/middleware";
|
||||||
|
|
||||||
|
interface Space {
|
||||||
|
space_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SpaceState {
|
||||||
|
currentSpace: Space | null;
|
||||||
|
setCurrentSpace: (space: Space | null) => void;
|
||||||
|
clear: () => void;
|
||||||
|
inSpace: () => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSpaceStore = create<SpaceState>()(
|
||||||
|
persist(
|
||||||
|
(set, get) => ({
|
||||||
|
currentSpace: null,
|
||||||
|
setCurrentSpace: (space) => set({ currentSpace: space }),
|
||||||
|
clear: () => set({ currentSpace: null }),
|
||||||
|
inSpace: () => {
|
||||||
|
const currentSpace = get().currentSpace;
|
||||||
|
return currentSpace !== null && currentSpace.space_id !== 0;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "space-storage",
|
||||||
|
storage: createJSONStorage(() => AsyncStorage),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
export function generateCode(
|
||||||
|
length: number = 6,
|
||||||
|
chars: string = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789",
|
||||||
|
): string {
|
||||||
|
let result = "";
|
||||||
|
const max = chars.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += chars.charAt(Math.floor(Math.random() * max));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
+143
@@ -0,0 +1,143 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Tree</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI";
|
||||||
|
background: #f5f7fa;
|
||||||
|
padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree {
|
||||||
|
width: 320px;
|
||||||
|
background: #fff;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.node {
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node:hover {
|
||||||
|
background: #f0f7ff;
|
||||||
|
color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node.active {
|
||||||
|
background: #e6f4ff;
|
||||||
|
color: #1677ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
width: 14px;
|
||||||
|
font-size: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow.open {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.children {
|
||||||
|
margin-left: 18px;
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.25s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.children.open {
|
||||||
|
max-height: 500px;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="tree">
|
||||||
|
|
||||||
|
<!-- 根 -->
|
||||||
|
<div class="tree-node">
|
||||||
|
|
||||||
|
<!-- ❗关键:整个 node 都可以选中 -->
|
||||||
|
<div class="node" onclick="selectNode(this)">
|
||||||
|
<span class="arrow" onclick="toggle(event, this)">▶</span>
|
||||||
|
系统管理
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="children open">
|
||||||
|
|
||||||
|
<div class="node" onclick="selectNode(this)">
|
||||||
|
用户管理
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="node" onclick="selectNode(this)">
|
||||||
|
角色管理
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 父节点 -->
|
||||||
|
<div class="tree-node">
|
||||||
|
|
||||||
|
<div class="node" onclick="selectNode(this)">
|
||||||
|
<span class="arrow" onclick="toggle(event, this)">▶</span>
|
||||||
|
权限管理
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="children">
|
||||||
|
|
||||||
|
<div class="node" onclick="selectNode(this)">
|
||||||
|
菜单权限
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="node" onclick="selectNode(this)">
|
||||||
|
按钮权限
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// ✅ 单选:保证父子都能选
|
||||||
|
function selectNode(el) {
|
||||||
|
document.querySelectorAll(".node.active")
|
||||||
|
.forEach(n => n.classList.remove("active"));
|
||||||
|
|
||||||
|
el.classList.add("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ 只控制展开(不影响选中)
|
||||||
|
function toggle(e, el) {
|
||||||
|
e.stopPropagation(); // ❗关键:阻止触发选中
|
||||||
|
|
||||||
|
const wrapper = el.closest(".tree-node");
|
||||||
|
const children = wrapper.querySelector(".children");
|
||||||
|
|
||||||
|
if (!children) return;
|
||||||
|
|
||||||
|
children.classList.toggle("open");
|
||||||
|
el.classList.toggle("open");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
+384
@@ -0,0 +1,384 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>Dialog Final</title>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.mask {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: rgba(0, 0, 0, 0.35);
|
||||||
|
z-index: 999;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-dialog {
|
||||||
|
position: fixed;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 12px 40px rgba(0, 0, 0, .25);
|
||||||
|
display: none;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-header {
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
cursor: move;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-title {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-btn {
|
||||||
|
display: flex;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-item {
|
||||||
|
padding: 0 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-item:hover {
|
||||||
|
background: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.win-body {
|
||||||
|
height: calc(100% - 40px);
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resizer {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.br {
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
cursor: nwse-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.r {
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 5px;
|
||||||
|
height: 100%;
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.b {
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 5px;
|
||||||
|
cursor: ns-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.l {
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 5px;
|
||||||
|
height: 100%;
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.t {
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 5px;
|
||||||
|
cursor: ns-resize;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<template id="file-template">
|
||||||
|
<div class="layout">
|
||||||
|
<div class="left">分类树</div>
|
||||||
|
<div class="middle">列表</div>
|
||||||
|
<div class="right">预览</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<button onclick="handleShowDialog()">
|
||||||
|
打开窗口
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function handleShowDialog() {
|
||||||
|
Dialog.open({
|
||||||
|
title: 'Test Window',
|
||||||
|
template: '#file-template',
|
||||||
|
width: '80%',
|
||||||
|
height: '80%'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dialog
|
||||||
|
const Dialog = (() => {
|
||||||
|
let $mask = $('<div class="mask"></div>').appendTo('body');
|
||||||
|
let $el = null;
|
||||||
|
let isMax = false;
|
||||||
|
let old = {};
|
||||||
|
let opt = null;
|
||||||
|
|
||||||
|
// ====== 工具函数 ======
|
||||||
|
function parseSize(val, max) {
|
||||||
|
if (typeof val === 'number') return val;
|
||||||
|
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
if (val.includes('%')) return max * parseFloat(val) / 100;
|
||||||
|
if (val.includes('vw')) return window.innerWidth * parseFloat(val) / 100;
|
||||||
|
if (val.includes('vh')) return window.innerHeight * parseFloat(val) / 100;
|
||||||
|
return parseFloat(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 核心方法 ======
|
||||||
|
function applySize() {
|
||||||
|
const maxW = window.innerWidth;
|
||||||
|
const maxH = window.innerHeight;
|
||||||
|
|
||||||
|
let w = parseSize(opt.width || 600, maxW);
|
||||||
|
let h = parseSize(opt.height || 400, maxH);
|
||||||
|
|
||||||
|
w = Math.min(w, maxW);
|
||||||
|
h = Math.min(h, maxH);
|
||||||
|
|
||||||
|
$el.css({
|
||||||
|
width: w,
|
||||||
|
height: h,
|
||||||
|
left: (maxW - w) / 2,
|
||||||
|
top: (maxH - h) / 2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindResize() {
|
||||||
|
$(window).on('resize.dialog', () => {
|
||||||
|
if (isMax) {
|
||||||
|
$el.css({
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const w = opt.width;
|
||||||
|
const h = opt.height;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(typeof w === 'string' && (w.includes('%') || w.includes('vw'))) ||
|
||||||
|
(typeof h === 'string' && (h.includes('%') || h.includes('vh')))
|
||||||
|
) {
|
||||||
|
applySize();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindEvents() {
|
||||||
|
const self = {
|
||||||
|
get el() { return $el; }
|
||||||
|
};
|
||||||
|
|
||||||
|
$el.find('.close').on('click', close);
|
||||||
|
|
||||||
|
// drag
|
||||||
|
let move = false, ox = 0, oy = 0;
|
||||||
|
|
||||||
|
$el.find('.win-header').on('mousedown', function (e) {
|
||||||
|
if (isMax) return;
|
||||||
|
|
||||||
|
move = true;
|
||||||
|
const pos = $el.position();
|
||||||
|
ox = e.clientX - pos.left;
|
||||||
|
oy = e.clientY - pos.top;
|
||||||
|
|
||||||
|
$(document).on('mousemove.drag', function (e) {
|
||||||
|
if (!move) return;
|
||||||
|
|
||||||
|
$el.css({
|
||||||
|
left: e.clientX - ox,
|
||||||
|
top: e.clientY - oy
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on('mouseup.drag', function () {
|
||||||
|
move = false;
|
||||||
|
$(document).off('.drag');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// resize
|
||||||
|
const MIN_W = 300;
|
||||||
|
const MIN_H = 200;
|
||||||
|
|
||||||
|
$el.find('.resizer').on('mousedown', function (e) {
|
||||||
|
if (isMax) return;
|
||||||
|
|
||||||
|
let type = $(this).attr('class').split(' ')[1];
|
||||||
|
|
||||||
|
const startX = e.clientX;
|
||||||
|
const startY = e.clientY;
|
||||||
|
|
||||||
|
const startW = $el.width();
|
||||||
|
const startH = $el.height();
|
||||||
|
const startL = parseFloat($el.css('left'));
|
||||||
|
const startT = parseFloat($el.css('top'));
|
||||||
|
|
||||||
|
const MAX_W = window.innerWidth;
|
||||||
|
const MAX_H = window.innerHeight;
|
||||||
|
|
||||||
|
function onMove(e) {
|
||||||
|
if (e.buttons !== 1) return;
|
||||||
|
|
||||||
|
let dx = e.clientX - startX;
|
||||||
|
let dy = e.clientY - startY;
|
||||||
|
|
||||||
|
let newW = startW;
|
||||||
|
let newH = startH;
|
||||||
|
let newL = startL;
|
||||||
|
let newT = startT;
|
||||||
|
|
||||||
|
if (type === 'br') { newW += dx; newH += dy; }
|
||||||
|
if (type === 'r') newW += dx;
|
||||||
|
if (type === 'b') newH += dy;
|
||||||
|
if (type === 'l') { newW -= dx; newL += dx; }
|
||||||
|
if (type === 't') { newH -= dy; newT += dy; }
|
||||||
|
|
||||||
|
if (newW < MIN_W) newW = MIN_W;
|
||||||
|
if (newH < MIN_H) newH = MIN_H;
|
||||||
|
|
||||||
|
if (newW > MAX_W) newW = MAX_W;
|
||||||
|
if (newH > MAX_H) newH = MAX_H;
|
||||||
|
|
||||||
|
$el.css({
|
||||||
|
width: newW,
|
||||||
|
height: newH,
|
||||||
|
left: newL,
|
||||||
|
top: newT
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUp() {
|
||||||
|
$(document).off('.resize');
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document)
|
||||||
|
.on('mousemove.resize', onMove)
|
||||||
|
.on('mouseup.resize', onUp);
|
||||||
|
});
|
||||||
|
|
||||||
|
// max
|
||||||
|
$el.find('.max').on('click', function () {
|
||||||
|
if (!isMax) {
|
||||||
|
old = {
|
||||||
|
width: $el.width(),
|
||||||
|
height: $el.height(),
|
||||||
|
left: $el.position().left,
|
||||||
|
top: $el.position().top
|
||||||
|
};
|
||||||
|
|
||||||
|
$el.css({
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight
|
||||||
|
});
|
||||||
|
|
||||||
|
isMax = true;
|
||||||
|
} else {
|
||||||
|
$el.css(old);
|
||||||
|
isMax = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroy() {
|
||||||
|
if ($el) {
|
||||||
|
$el.remove();
|
||||||
|
$el = null;
|
||||||
|
}
|
||||||
|
isMax = false;
|
||||||
|
$mask.off().hide();
|
||||||
|
$(window).off('resize.dialog');
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== API ======
|
||||||
|
function open(o) {
|
||||||
|
destroy();
|
||||||
|
opt = o;
|
||||||
|
|
||||||
|
$el = $(`
|
||||||
|
<div class="win-dialog">
|
||||||
|
<div class="win-header">
|
||||||
|
<div class="win-title"></div>
|
||||||
|
<div class="win-btn">
|
||||||
|
<div class="btn-item max">MAX</div>
|
||||||
|
<div class="btn-item close">X</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="win-body"></div>
|
||||||
|
</div>
|
||||||
|
`).appendTo('body');
|
||||||
|
|
||||||
|
$el.find('.win-title').text(opt.title || 'Dialog');
|
||||||
|
const $body = $el.find('.win-body');
|
||||||
|
|
||||||
|
if (opt.template) {
|
||||||
|
const tpl = document.querySelector(opt.template);
|
||||||
|
const node = tpl.content.cloneNode(true);
|
||||||
|
$body.empty().append(node);
|
||||||
|
}
|
||||||
|
else if (opt.content instanceof HTMLElement) {
|
||||||
|
$body.empty().append(opt.content);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$body.html(opt.content || '');
|
||||||
|
}
|
||||||
|
applySize();
|
||||||
|
|
||||||
|
$mask.fadeIn(120);
|
||||||
|
$el.fadeIn(180);
|
||||||
|
|
||||||
|
bindEvents();
|
||||||
|
bindResize();
|
||||||
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
$el?.fadeOut(120);
|
||||||
|
$mask.fadeOut(120);
|
||||||
|
|
||||||
|
setTimeout(() => destroy(), 120);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ====== 单例导出 ======
|
||||||
|
return {
|
||||||
|
open,
|
||||||
|
close
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Reference in new issue
Block a user