commit 8e7f1f17f0079f5d36affb0a16188353250990db Author: yml Date: Fri May 22 15:05:28 2026 +0800 第 0 阶段:项目初始化 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1bdb9b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +.snow +.vscode +.idea +*.log +*.tmp +*.bak + +# Environment +.env +.env.* +!.env.example + +# Go +bin/ +coverage.out + +# Node / Vue +node_modules/ +dist/ +.vite/ +*.tsbuildinfo + +# Docker / local data +.docker-data/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..b506e8e --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# HFB Sys + +哈夫币租号平台,面向《三角洲行动》账号租赁场景。 + +## 开发方式 + +- 后端:Go + Gin + GORM + MySQL + Redis +- 前端:Vue 3 + TypeScript + Vite + Element Plus +- 开发依赖:Docker Compose 启动 MySQL、Redis、MinIO + +## 快速开始 + +```bash +docker compose -f deploy/docker-compose.dev.yml up -d + +cd backend +cp .env.example .env +go run ./cmd/api + +cd ../frontend +npm install +npm run dev +``` + +## 文档 + +- [项目计划](docs/project-plan.md) +- [数据库设计](docs/database.md) +- [接口规划](docs/api.md) +- [业务规则](docs/business-rules.md) diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..105ce80 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,11 @@ +APP_ENV=development +APP_ADDR=:8080 + +MYSQL_DSN=hfb:secret@tcp(127.0.0.1:3306)/hfb_sys?charset=utf8mb4&parseTime=True&loc=Local +REDIS_ADDR=127.0.0.1:6379 +REDIS_PASSWORD= +REDIS_DB=0 + +JWT_SECRET=change-me +STORAGE_ENDPOINT=http://127.0.0.1:9000 +STORAGE_BUCKET=hfb-sys diff --git a/backend/Dockerfile b/backend/Dockerfile new file mode 100644 index 0000000..3a4803f --- /dev/null +++ b/backend/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.26-alpine AS build + +WORKDIR /src +COPY go.mod go.sum* ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 GOOS=linux go build -o /out/hfb-api ./cmd/api + +FROM alpine:3.22 + +WORKDIR /app +COPY --from=build /out/hfb-api /app/hfb-api +EXPOSE 8080 +CMD ["/app/hfb-api"] diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go new file mode 100644 index 0000000..b4128af --- /dev/null +++ b/backend/cmd/api/main.go @@ -0,0 +1,54 @@ +package main + +import ( + "context" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "hfb_sys/backend/internal/config" + "hfb_sys/backend/internal/router" + + "go.uber.org/zap" +) + +func main() { + cfg := config.Load() + logger, err := zap.NewProduction() + if cfg.AppEnv == "development" { + logger, err = zap.NewDevelopment() + } + if err != nil { + panic(err) + } + defer func() { + _ = logger.Sync() + }() + + engine := router.New(cfg, logger) + server := &http.Server{ + Addr: cfg.AppAddr, + Handler: engine, + ReadHeaderTimeout: 5 * time.Second, + } + + go func() { + logger.Info("api server starting", zap.String("addr", cfg.AppAddr)) + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + logger.Fatal("api server failed", zap.Error(err)) + } + }() + + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + if err := server.Shutdown(ctx); err != nil { + logger.Fatal("api server shutdown failed", zap.Error(err)) + } + logger.Info("api server stopped") +} diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..c27ca0c --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,52 @@ +module hfb_sys/backend + +go 1.26 + +require ( + github.com/gin-gonic/gin v1.11.0 + github.com/redis/go-redis/v9 v9.17.0 + go.uber.org/zap v1.27.0 + gorm.io/driver/mysql v1.6.0 + gorm.io/gorm v1.31.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/bytedance/sonic v1.14.0 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gabriel-vasile/mimetype v1.4.10 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.28.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/quic-go v0.54.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.0 // indirect + go.uber.org/mock v0.5.0 // indirect + go.uber.org/multierr v1.10.0 // indirect + golang.org/x/arch v0.20.0 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/mod v0.27.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/sys v0.36.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/tools v0.36.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect +) diff --git a/backend/go.sum b/backend/go.sum new file mode 100644 index 0000000..5e3cfae --- /dev/null +++ b/backend/go.sum @@ -0,0 +1,116 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/sonic v1.14.0 h1:/OfKt8HFw0kh2rj8N0F6C/qPGRESq0BbaNZgcNXXzQQ= +github.com/bytedance/sonic v1.14.0/go.mod h1:WoEbx8WTcFJfzCe0hbmyTGrfjt8PzNEBdxlNUO24NhA= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0= +github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688= +github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= +github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg= +github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY= +github.com/redis/go-redis/v9 v9.17.0 h1:K6E+ZlYN95KSMmZeEQPbU/c++wfmEvfFB17yEAq/VhM= +github.com/redis/go-redis/v9 v9.17.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= +go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= +go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= +golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= +golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= +golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= +golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= +golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= +golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= +google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= +google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go new file mode 100644 index 0000000..95032d3 --- /dev/null +++ b/backend/internal/config/config.go @@ -0,0 +1,58 @@ +package config + +import ( + "os" + "strconv" +) + +type Config struct { + AppEnv string + AppAddr string + MySQLDSN string + RedisAddr string + RedisPassword string + RedisDB int + JWTSecret string + Storage StorageConfig +} + +type StorageConfig struct { + Endpoint string + Bucket string +} + +func Load() Config { + return Config{ + AppEnv: getEnv("APP_ENV", "development"), + AppAddr: getEnv("APP_ADDR", ":8080"), + MySQLDSN: getEnv("MYSQL_DSN", "hfb:secret@tcp(127.0.0.1:3306)/hfb_sys?charset=utf8mb4&parseTime=True&loc=Local"), + RedisAddr: getEnv("REDIS_ADDR", "127.0.0.1:6379"), + RedisPassword: getEnv("REDIS_PASSWORD", ""), + RedisDB: getEnvInt("REDIS_DB", 0), + JWTSecret: getEnv("JWT_SECRET", "change-me"), + Storage: StorageConfig{ + Endpoint: getEnv("STORAGE_ENDPOINT", "http://127.0.0.1:9000"), + Bucket: getEnv("STORAGE_BUCKET", "hfb-sys"), + }, + } +} + +func getEnv(key, fallback string) string { + value := os.Getenv(key) + if value == "" { + return fallback + } + return value +} + +func getEnvInt(key string, fallback int) int { + value := os.Getenv(key) + if value == "" { + return fallback + } + parsed, err := strconv.Atoi(value) + if err != nil { + return fallback + } + return parsed +} diff --git a/backend/internal/database/mysql.go b/backend/internal/database/mysql.go new file mode 100644 index 0000000..ce6c3ef --- /dev/null +++ b/backend/internal/database/mysql.go @@ -0,0 +1,10 @@ +package database + +import ( + "gorm.io/driver/mysql" + "gorm.io/gorm" +) + +func OpenMySQL(dsn string) (*gorm.DB, error) { + return gorm.Open(mysql.Open(dsn), &gorm.Config{}) +} diff --git a/backend/internal/database/redis.go b/backend/internal/database/redis.go new file mode 100644 index 0000000..de88302 --- /dev/null +++ b/backend/internal/database/redis.go @@ -0,0 +1,25 @@ +package database + +import ( + "context" + + "github.com/redis/go-redis/v9" +) + +type RedisConfig struct { + Addr string + Password string + DB int +} + +func OpenRedis(ctx context.Context, cfg RedisConfig) (*redis.Client, error) { + client := redis.NewClient(&redis.Options{ + Addr: cfg.Addr, + Password: cfg.Password, + DB: cfg.DB, + }) + if err := client.Ping(ctx).Err(); err != nil { + return nil, err + } + return client, nil +} diff --git a/backend/internal/handler/health.go b/backend/internal/handler/health.go new file mode 100644 index 0000000..256c445 --- /dev/null +++ b/backend/internal/handler/health.go @@ -0,0 +1,24 @@ +package handler + +import ( + "net/http" + "time" + + "github.com/gin-gonic/gin" +) + +type HealthHandler struct { + startedAt time.Time +} + +func NewHealthHandler() *HealthHandler { + return &HealthHandler{startedAt: time.Now()} +} + +func (h *HealthHandler) Check(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + "service": "hfb-api", + "started_at": h.startedAt.Format(time.RFC3339), + }) +} diff --git a/backend/internal/middleware/request_logger.go b/backend/internal/middleware/request_logger.go new file mode 100644 index 0000000..ea8cd4e --- /dev/null +++ b/backend/internal/middleware/request_logger.go @@ -0,0 +1,23 @@ +package middleware + +import ( + "time" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +func RequestLogger(logger *zap.Logger) gin.HandlerFunc { + return func(c *gin.Context) { + start := time.Now() + c.Next() + + logger.Info("http request", + zap.String("method", c.Request.Method), + zap.String("path", c.Request.URL.Path), + zap.Int("status", c.Writer.Status()), + zap.Duration("latency", time.Since(start)), + zap.String("client_ip", c.ClientIP()), + ) + } +} diff --git a/backend/internal/modules/auth/README.md b/backend/internal/modules/auth/README.md new file mode 100644 index 0000000..bdefceb --- /dev/null +++ b/backend/internal/modules/auth/README.md @@ -0,0 +1,3 @@ +# Auth Module + +手机号短信登录、JWT、验证码限流。 diff --git a/backend/internal/modules/dispute/README.md b/backend/internal/modules/dispute/README.md new file mode 100644 index 0000000..99c7b72 --- /dev/null +++ b/backend/internal/modules/dispute/README.md @@ -0,0 +1,3 @@ +# Dispute Module + +纠纷举证、客服仲裁、裁决落账。 diff --git a/backend/internal/modules/listing/README.md b/backend/internal/modules/listing/README.md new file mode 100644 index 0000000..5c890c7 --- /dev/null +++ b/backend/internal/modules/listing/README.md @@ -0,0 +1,3 @@ +# Listing Module + +账号发布、资产快照、审核、上下架。 diff --git a/backend/internal/modules/order/README.md b/backend/internal/modules/order/README.md new file mode 100644 index 0000000..1202d40 --- /dev/null +++ b/backend/internal/modules/order/README.md @@ -0,0 +1,3 @@ +# Order Module + +订单状态机、账号交接、租期归还、超时处理。 diff --git a/backend/internal/modules/wallet/README.md b/backend/internal/modules/wallet/README.md new file mode 100644 index 0000000..35701d8 --- /dev/null +++ b/backend/internal/modules/wallet/README.md @@ -0,0 +1,3 @@ +# Wallet Module + +押金冻结、租金结算、不可变资金流水。 diff --git a/backend/internal/router/router.go b/backend/internal/router/router.go new file mode 100644 index 0000000..a8ade92 --- /dev/null +++ b/backend/internal/router/router.go @@ -0,0 +1,30 @@ +package router + +import ( + "hfb_sys/backend/internal/config" + "hfb_sys/backend/internal/handler" + "hfb_sys/backend/internal/middleware" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +func New(cfg config.Config, logger *zap.Logger) *gin.Engine { + if cfg.AppEnv == "production" { + gin.SetMode(gin.ReleaseMode) + } + + engine := gin.New() + engine.Use(gin.Recovery()) + engine.Use(middleware.RequestLogger(logger)) + + health := handler.NewHealthHandler() + engine.GET("/health", health.Check) + + api := engine.Group("/api") + { + api.GET("/health", health.Check) + } + + return engine +} diff --git a/backend/migrations/000001_init.sql b/backend/migrations/000001_init.sql new file mode 100644 index 0000000..c680a3a --- /dev/null +++ b/backend/migrations/000001_init.sql @@ -0,0 +1,248 @@ +CREATE TABLE users ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + phone VARCHAR(32) NOT NULL, + nickname VARCHAR(64) NOT NULL DEFAULT '', + avatar_url VARCHAR(512) NOT NULL DEFAULT '', + realname_status VARCHAR(32) NOT NULL DEFAULT 'unverified', + risk_status VARCHAR(32) NOT NULL DEFAULT 'normal', + credit_score INT NOT NULL DEFAULT 100, + status VARCHAR(32) NOT NULL DEFAULT 'active', + last_login_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_users_phone (phone) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE user_realname ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT UNSIGNED NOT NULL, + provider VARCHAR(32) NOT NULL, + provider_order_no VARCHAR(128) NOT NULL DEFAULT '', + status VARCHAR(32) NOT NULL DEFAULT 'pending', + masked_name VARCHAR(64) NOT NULL DEFAULT '', + masked_id_no VARCHAR(64) NOT NULL DEFAULT '', + verified_at DATETIME NULL, + fail_reason VARCHAR(255) NOT NULL DEFAULT '', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_user_realname_user_id (user_id), + KEY idx_user_realname_provider_order_no (provider_order_no) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE game_accounts ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + owner_id BIGINT UNSIGNED NOT NULL, + game_name VARCHAR(64) NOT NULL DEFAULT 'delta_force', + server_region VARCHAR(64) NOT NULL, + login_platform VARCHAR(64) NOT NULL, + title VARCHAR(128) NOT NULL, + description TEXT NULL, + rank_level VARCHAR(64) NOT NULL DEFAULT '', + haf_coin_amount BIGINT NOT NULL DEFAULT 0, + asset_summary JSON NULL, + season_tags JSON NULL, + screenshot_urls JSON NULL, + status VARCHAR(32) NOT NULL DEFAULT 'draft', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_game_accounts_owner_id (owner_id), + KEY idx_game_accounts_filter (game_name, server_region, login_platform) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE rental_listings ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + account_id BIGINT UNSIGNED NOT NULL, + owner_id BIGINT UNSIGNED NOT NULL, + price_hourly DECIMAL(12,2) NOT NULL DEFAULT 0.00, + price_daily DECIMAL(12,2) NOT NULL DEFAULT 0.00, + price_weekly DECIMAL(12,2) NOT NULL DEFAULT 0.00, + deposit_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, + min_rent_hours INT NOT NULL DEFAULT 1, + max_rent_hours INT NOT NULL DEFAULT 168, + status VARCHAR(32) NOT NULL DEFAULT 'draft', + review_status VARCHAR(32) NOT NULL DEFAULT 'none', + review_reason VARCHAR(255) NOT NULL DEFAULT '', + published_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_rental_listings_account_id (account_id), + KEY idx_rental_listings_owner_id (owner_id), + KEY idx_rental_listings_filter (status, review_status, price_hourly) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE rental_orders ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + order_no VARCHAR(64) NOT NULL, + listing_id BIGINT UNSIGNED NOT NULL, + account_id BIGINT UNSIGNED NOT NULL, + owner_id BIGINT UNSIGNED NOT NULL, + renter_id BIGINT UNSIGNED NOT NULL, + rent_start_at DATETIME NULL, + rent_end_at DATETIME NULL, + rent_hours INT NOT NULL, + rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, + deposit_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00, + platform_fee DECIMAL(12,2) NOT NULL DEFAULT 0.00, + account_snapshot JSON NULL, + status VARCHAR(32) NOT NULL DEFAULT 'pending_confirm', + handoff_status VARCHAR(32) NOT NULL DEFAULT 'none', + settlement_status VARCHAR(32) NOT NULL DEFAULT 'unsettled', + owner_settled_at DATETIME NULL, + settled_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_rental_orders_order_no (order_no), + KEY idx_rental_orders_renter_status (renter_id, status), + KEY idx_rental_orders_owner_status (owner_id, status), + KEY idx_rental_orders_listing_id (listing_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE handoff_records ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + order_id BIGINT UNSIGNED NOT NULL, + from_user_id BIGINT UNSIGNED NOT NULL, + to_user_id BIGINT UNSIGNED NOT NULL, + type VARCHAR(32) NOT NULL, + content TEXT NULL, + attachment_urls JSON NULL, + confirmed_by_renter_at DATETIME NULL, + confirmed_by_owner_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + KEY idx_handoff_records_order_id (order_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE wallet_accounts ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT UNSIGNED NOT NULL, + available_balance DECIMAL(12,2) NOT NULL DEFAULT 0.00, + frozen_balance DECIMAL(12,2) NOT NULL DEFAULT 0.00, + status VARCHAR(32) NOT NULL DEFAULT 'active', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_wallet_accounts_user_id (user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE wallet_ledger ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + ledger_no VARCHAR(64) NOT NULL, + user_id BIGINT UNSIGNED NOT NULL, + order_id BIGINT UNSIGNED NULL, + direction VARCHAR(16) NOT NULL, + amount DECIMAL(12,2) NOT NULL, + balance_after DECIMAL(12,2) NOT NULL, + balance_type VARCHAR(32) NOT NULL, + biz_type VARCHAR(32) NOT NULL, + biz_no VARCHAR(64) NOT NULL, + remark VARCHAR(255) NOT NULL DEFAULT '', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_wallet_ledger_ledger_no (ledger_no), + KEY idx_wallet_ledger_user_created (user_id, created_at), + KEY idx_wallet_ledger_biz (biz_type, biz_no) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE disputes ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + order_id BIGINT UNSIGNED NOT NULL, + initiator_id BIGINT UNSIGNED NOT NULL, + target_user_id BIGINT UNSIGNED NOT NULL, + type VARCHAR(32) NOT NULL, + status VARCHAR(32) NOT NULL DEFAULT 'open', + description TEXT NULL, + evidence_urls JSON NULL, + arbitration_result VARCHAR(32) NOT NULL DEFAULT '', + arbitration_remark TEXT NULL, + handled_by BIGINT UNSIGNED NULL, + handled_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + KEY idx_disputes_order_id (order_id), + KEY idx_disputes_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE notifications ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + user_id BIGINT UNSIGNED NOT NULL, + type VARCHAR(32) NOT NULL, + title VARCHAR(128) NOT NULL, + content TEXT NULL, + biz_type VARCHAR(32) NOT NULL DEFAULT '', + biz_id BIGINT UNSIGNED NULL, + read_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + KEY idx_notifications_user_read_created (user_id, read_at, created_at) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE audit_logs ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + actor_type VARCHAR(32) NOT NULL, + actor_id BIGINT UNSIGNED NOT NULL, + action VARCHAR(64) NOT NULL, + biz_type VARCHAR(32) NOT NULL, + biz_id BIGINT UNSIGNED NULL, + ip VARCHAR(64) NOT NULL DEFAULT '', + user_agent VARCHAR(512) NOT NULL DEFAULT '', + detail JSON NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + KEY idx_audit_logs_actor_created (actor_id, created_at), + KEY idx_audit_logs_biz (biz_type, biz_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE admin_users ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + username VARCHAR(64) NOT NULL, + password_hash VARCHAR(255) NOT NULL, + nickname VARCHAR(64) NOT NULL DEFAULT '', + status VARCHAR(32) NOT NULL DEFAULT 'active', + last_login_at DATETIME NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_admin_users_username (username) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE roles ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + code VARCHAR(64) NOT NULL, + name VARCHAR(64) NOT NULL, + description VARCHAR(255) NOT NULL DEFAULT '', + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_roles_code (code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE permissions ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + code VARCHAR(128) NOT NULL, + name VARCHAR(64) NOT NULL, + resource VARCHAR(128) NOT NULL, + action VARCHAR(64) NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_permissions_code (code) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE admin_user_roles ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + admin_user_id BIGINT UNSIGNED NOT NULL, + role_id BIGINT UNSIGNED NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_admin_user_roles_user_role (admin_user_id, role_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE role_permissions ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + role_id BIGINT UNSIGNED NOT NULL, + permission_id BIGINT UNSIGNED NOT NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + UNIQUE KEY uk_role_permissions_role_permission (role_id, permission_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE system_configs ( + id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, + `key` VARCHAR(128) NOT NULL, + `value` TEXT NOT NULL, + description VARCHAR(255) NOT NULL DEFAULT '', + updated_by BIGINT UNSIGNED NULL, + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY uk_system_configs_key (`key`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/deploy/docker-compose.dev.yml b/deploy/docker-compose.dev.yml new file mode 100644 index 0000000..cfd0845 --- /dev/null +++ b/deploy/docker-compose.dev.yml @@ -0,0 +1,59 @@ +services: + mysql: + image: mysql:8.4 + container_name: hfb-mysql + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: rootsecret + MYSQL_DATABASE: hfb_sys + MYSQL_USER: hfb + MYSQL_PASSWORD: secret + TZ: Asia/Shanghai + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-prootsecret"] + interval: 10s + timeout: 5s + retries: 10 + + redis: + image: redis:7.4-alpine + container_name: hfb-redis + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis_data:/data + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 10 + + minio: + image: minio/minio:RELEASE.2025-09-07T16-13-09Z + container_name: hfb-minio + restart: unless-stopped + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: minioadmin + MINIO_ROOT_PASSWORD: minioadmin + TZ: Asia/Shanghai + ports: + - "9000:9000" + - "9001:9001" + volumes: + - minio_data:/data + healthcheck: + test: ["CMD", "mc", "ready", "local"] + interval: 10s + timeout: 5s + retries: 10 + +volumes: + mysql_data: + redis_data: + minio_data: diff --git a/deploy/docker-compose.prod.yml b/deploy/docker-compose.prod.yml new file mode 100644 index 0000000..e36eca4 --- /dev/null +++ b/deploy/docker-compose.prod.yml @@ -0,0 +1,19 @@ +services: + backend: + build: + context: ../backend + restart: unless-stopped + env_file: + - ../backend/.env + ports: + - "8080:8080" + + frontend: + build: + context: .. + dockerfile: frontend/Dockerfile + restart: unless-stopped + depends_on: + - backend + ports: + - "80:80" diff --git a/deploy/nginx/default.conf b/deploy/nginx/default.conf new file mode 100644 index 0000000..d18f25c --- /dev/null +++ b/deploy/nginx/default.conf @@ -0,0 +1,24 @@ +server { + listen 80; + server_name _; + + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /api/ { + proxy_pass http://backend:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /health { + proxy_pass http://backend:8080/health; + proxy_set_header Host $host; + } +} diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..fd97198 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,14 @@ +# API 规划 + +API 规划以 [项目计划](project-plan.md) 第 10 章为准。 + +一期接口按模块推进: + +- 认证:短信验证码、手机号登录、Token 刷新、退出登录。 +- 实名:发起认证、查询状态、服务回调。 +- 发布:创建、修改、提交审核、下架、列表、详情。 +- 订单:创建、取消、确认收号、提交归还、确认归还、发起申诉。 +- 交接:提交交接说明、查看交接记录。 +- 钱包:余额、流水、提现预留。 +- 通知:站内信列表、标记已读。 +- 后台:审核、订单、仲裁、资金流水、系统配置、审计日志。 diff --git a/docs/business-rules.md b/docs/business-rules.md new file mode 100644 index 0000000..fc2ba29 --- /dev/null +++ b/docs/business-rules.md @@ -0,0 +1,12 @@ +# 业务规则 + +业务规则以 [项目计划](project-plan.md) 为准。 + +一期关键边界: + +- 哈夫币是游戏账号内资产,不是平台币。 +- 哈夫币数量由号主手动填报,订单创建时生成账号资产快照。 +- 平台不保存游戏账号密码,不代收验证码,不绕过游戏安全机制。 +- 账号交接采用号主手动交接。 +- 资金先做账务模型,不直接接真实支付和提现。 +- 押金、交接超时、归还超时、短信限流等阈值进入系统配置。 diff --git a/docs/database.md b/docs/database.md new file mode 100644 index 0000000..ddf0463 --- /dev/null +++ b/docs/database.md @@ -0,0 +1,25 @@ +# 数据库设计 + +数据库设计以 [项目计划](project-plan.md) 第 9 章为准。 + +一期优先落表: + +- `users` +- `user_realname` +- `game_accounts` +- `rental_listings` +- `rental_orders` +- `handoff_records` +- `wallet_accounts` +- `wallet_ledger` +- `disputes` +- `notifications` +- `audit_logs` +- `admin_users` +- `roles` +- `permissions` +- `admin_user_roles` +- `role_permissions` +- `system_configs` + +后续实现迁移时,资金相关表必须保证流水只追加,订单相关表必须保留账号资产快照。 diff --git a/docs/project-plan.md b/docs/project-plan.md new file mode 100644 index 0000000..f58757f --- /dev/null +++ b/docs/project-plan.md @@ -0,0 +1,926 @@ +# 哈夫币租号平台项目计划 + +## 1. 项目定位与业务边界 + +本项目是面向《三角洲行动》账号租赁场景的前后端分离平台,核心能力包括手机号登录、实名认证、号主发布租号、租客下单、账号交接、租期归还、押金结算、纠纷仲裁和后台审核管理。 + +哈夫币在本系统中只被定义为游戏账号内资产,不是平台自发行虚拟币,不提供平台内铸币、转赠、交易、提现或链上能力。平台只记录账号资产描述、租赁订单、押金、租金、结算流水和纠纷处理结果。 + +一期不做自动上号器、不自动改密、不强制踢下线、不绕过游戏安全机制,也不开发外挂、盗号、破解、规避封禁等能力。账号租赁可能涉及游戏官方协议、账号安全、未成年人保护、支付合规和纠纷处理风险,上线前需要补充用户协议、隐私政策、交易规则和合规评估。 + +## 2. 技术栈与开发部署方式 + +前端技术栈: + +- Vue 3 +- TypeScript +- Vite +- Element Plus +- Pinia +- Vue Router +- Axios + +后端技术栈: + +- Go +- Gin +- GORM +- MySQL 8 +- Redis +- JWT +- Casbin +- Zap 或 Zerolog +- Viper + +开源借鉴策略: + +- 项目路线为借鉴成熟开源后台的通用工程能力,租号业务核心从头自研。 +- 可重点参考 `gin-vue-admin`、`go-admin` 这类 Go/Vue 后台项目的 JWT、Casbin、RBAC、菜单权限、Swagger、GORM 分层、配置管理、日志、文件上传和基础 CRUD 组织方式。 +- 不直接二开租号源码,不把未知项目的交易、资金和风控逻辑作为基础。 +- 不直接接入未知质量的租号系统源码,不复用不可信的订单、资金、押金、交接或仲裁逻辑。 +- 必须自研的核心模块包括租号发布、订单状态机、账号交接、押金冻结、资金流水、纠纷仲裁、风控规则和哈夫币资产快照。 +- 后台 UI 布局、菜单管理、权限管理可以参考开源项目;用户端体验、号主端流程和租号业务页面从头设计。 + +文件存储: + +- 开发环境使用 MinIO。 +- 生产环境使用阿里云 OSS 或腾讯云 COS。 +- 文件主要用于账号资产截图、租赁前后凭证、纠纷证据、后台公告图片、实名认证服务回执或材料引用。 +- 一期不要求平台长期保存身份证原图,实名认证资料优先交由第三方实名服务处理。 + +开发方式: + +- 本机直接运行 Go 后端,便于调试和热重载。 +- 本机直接运行 Vue 前端,便于页面开发。 +- Docker Compose 启动 MySQL、Redis、MinIO。 +- 后端可使用 `air` 做本地热重载。 +- 前端使用 `pnpm dev` 本地开发。 + +生产部署: + +- 后端构建 Docker 镜像部署。 +- 前端构建静态资源后由 Nginx 托管。 +- MySQL 生产优先使用云数据库。 +- Redis 生产优先使用云 Redis。 +- 文件存储生产使用 OSS/COS。 +- Nginx 负责 HTTPS、静态资源、反向代理和基础限流。 + +## 3. 三角洲行动特有规则 + +哈夫币填报: + +- 一期不接游戏 API 同步,哈夫币数量由号主手动填报。 +- 号主发布时必须上传租前资产截图,作为哈夫币数量、段位、资产描述和纠纷仲裁依据。 +- `game_accounts.haf_coin_amount` 表示号主最近一次填报值,不代表实时值。 +- 下单时必须生成订单账号快照,记录哈夫币数量、区服、登录平台、段位、赛季限定资产、账号截图和资产描述。 +- 租赁前后哈夫币差异以订单快照、交接记录、租前租后截图和客服仲裁为准。 + +安全验证: + +- 如果账号登录需要安全锁、二次验证、手机验证码或设备确认,由号主在交接阶段手动配合。 +- 号主超时未配合安全验证时,租客可取消订单或发起申诉。 +- 平台不保存游戏账号密码,不代收验证码,不绕过游戏登录、二次验证、安全锁或风控机制。 + +区服与资产: + +- 区服资产默认隔离,商品必须明确区服。 +- 不同区服的哈夫币、段位和资产不合并展示。 +- 跨区服租赁不作为一期核心能力。 +- 赛季限定资产以标签和资产说明形式展示,可作为号主定价和押金建议参考,但一期不参与自动估值。 + +## 4. 核心业务模块 + +用户与认证: + +- 手机号短信验证码登录。 +- JWT 登录态。 +- 用户角色包括租客、号主、管理员相关角色。 +- 支持实名状态、风控状态、冻结状态。 + +实名认证: + +- 接入阿里云实人认证或腾讯云身份认证。 +- 用户发起认证后,平台记录认证渠道、认证流水号、认证状态和脱敏结果。 +- 未实名用户不能发布租号,后台可配置是否限制未实名租客下单。 + +租号发布: + +- 号主实名后可发布账号。 +- 发布内容包括游戏、区服、平台、段位、哈夫币数量、账号资产描述、截图、租金、押金、租期范围。 +- 商品发布后进入待审核状态,后台审核通过后才能上架。 + +订单租赁: + +- 租客选择商品和租期后创建订单。 +- 系统锁定商品库存,避免同一账号被重复出租。 +- 订单流转覆盖待确认、待交接、租赁中、待归还确认、已完成、已取消、申诉中。 + +钱包账务: + +- 一期先做账务模型,不直接接真实支付和提现。 +- 系统记录租金、押金、冻结、解冻、扣款、退款、结算等流水。 +- 后续可接微信支付、支付宝支付、人工打款或第三方提现通道。 + +后台管理: + +- 用户管理。 +- 实名状态查看。 +- 商品审核。 +- 订单管理。 +- 纠纷仲裁。 +- 资金流水。 +- 风控名单。 +- 审计日志。 + +## 5. 账号交接与回收流程 + +一期采用号主手动交接模式,平台不保存游戏账号密码,不托管明文账号凭据。 + +交接流程: + +1. 租客创建订单,系统校验商品状态、租期、押金和余额账务状态。 +2. 订单确认后,商品进入锁定状态,订单进入待交接。 +3. 号主在订单内提交交接说明,例如登录方式、注意事项、联系说明或外部交接备注。 +4. 租客确认收到账号后,订单进入租赁中,并记录租赁开始时间和预计结束时间。 +5. 租期即将结束时,系统通过站内信和短信提醒租客归还。 +6. 租客提交归还申请,上传必要的租后截图或说明。 +7. 号主确认账号状态无误后,订单进入结算。 +8. 如任一方对交接、使用、归还或资产变化有异议,订单进入申诉仲裁。 + +回收边界: + +- 平台不自动修改账号密码。 +- 平台不强制踢下线。 +- 平台不绕过游戏登录、二次验证、安全锁或风控机制。 +- 平台不承诺技术性收回账号,只通过订单规则、押金、证据和仲裁处理争议。 + +超时机制: + +- 号主待交接超时:订单进入待交接后,默认 30 分钟未提交交接说明,租客可取消订单或发起申诉。 +- 租客确认收号超时:号主提交交接说明后,默认 30 分钟未确认收号,订单进入客服介入,不自动开始租期。 +- 租客归还超时:超过租期结束时间仍未提交归还,系统提醒后进入逾期中,号主可发起申诉。 +- 号主确认归还超时:租客提交归还后,默认 2 小时未确认,订单进入自动确认候选或客服复核。 +- 所有超时阈值必须进入 `system_configs` 管理,后台可调整。 + +订单状态机: + +| From | To | 触发者 | 条件 | +| --- | --- | --- | --- | +| 待确认 | 待交接 | 系统 | 订单确认,商品锁定,账务校验通过 | +| 待确认 | 已取消 | 租客/系统 | 租客主动取消或确认超时 | +| 待交接 | 待收号确认 | 号主 | 号主提交交接说明 | +| 待交接 | 已取消 | 租客 | 号主交接超时且未进入申诉 | +| 待交接 | 申诉中 | 租客/号主 | 交接争议或安全验证无法完成 | +| 待收号确认 | 租赁中 | 租客 | 租客确认收到账号并可正常登录 | +| 待收号确认 | 申诉中 | 租客/系统 | 租客确认超时、无法登录或描述不符 | +| 租赁中 | 逾期中 | 系统 | 超过租期结束时间仍未提交归还 | +| 租赁中 | 待归还确认 | 租客 | 租客提交归还和租后凭证 | +| 逾期中 | 待归还确认 | 租客 | 租客补交归还申请 | +| 逾期中 | 申诉中 | 号主/系统 | 逾期未归还或疑似资产损失 | +| 待归还确认 | 已完成 | 号主/系统 | 号主确认归还,或超时后客服/系统确认 | +| 待归还确认 | 申诉中 | 号主/租客 | 归还争议、资产损失或封号异常 | +| 申诉中 | 已完成 | 客服 | 仲裁为正常完成或部分扣款后完成 | +| 申诉中 | 已取消 | 客服 | 仲裁为订单取消和退款 | +| 申诉中 | 已关闭 | 客服 | 仲裁为异常关闭、冻结用户或冻结商品 | + +终态规则: + +- `已完成`、`已取消`、`已关闭` 为终态,普通业务流程不能逆转。 +- 终态订单如需调整,只能通过后台复核流程生成新的资金流水和审计日志,不能直接改旧流水。 +- `申诉中` 会冻结相关押金和结算金额,直到客服仲裁。 + +## 6. 押金、价格与结算模型 + +定价模型: + +- 租金由号主自主设置。 +- 支持按小时、按天、按周配置价格。 +- 平台可在后续提供建议价,但一期不强制。 +- 商品需配置最短租期和最长租期。 + +押金模型: + +- 押金由号主设置,平台校验最低押金。 +- 最低押金可根据账号估值、哈夫币数量、稀有资产、租期长度、号主历史纠纷率、租客风险等级给出动态建议。 +- 一期先实现规则化最低押金,动态建议可后续迭代。 +- 押金不足以覆盖损失时,订单进入人工仲裁,不自动产生超额扣款。 + +账务模型: + +- 一期先做平台内账务,不接真实支付和提现。 +- 钱包流水采用不可变账本设计。 +- 余额可通过流水汇总或账户余额表事务更新获得。 +- 每笔余额变化必须有业务单据来源,例如订单、押金、仲裁、退款、结算。 +- 平台抽成比例、号主结算周期、提现门槛作为系统配置项预留。 + +结算规则: + +- 正常完成订单后,租金按平台抽成规则分账给号主。 +- 押金在号主确认归还后释放给租客。 +- 如发生资产损失、无法登录、封号或超时归还,可由客服仲裁后扣除部分或全部押金。 +- 如号主虚假描述、超时未交接或账号无法使用,可裁定全额或部分退款。 +- 仲裁导致的扣款、退款、赔付必须生成资金流水和审计日志。 + +## 7. 纠纷仲裁规则 + +纠纷类型: + +- 无法登录。 +- 账号描述不符。 +- 哈夫币数量争议。 +- 账号资产损失。 +- 账号封禁或异常。 +- 号主超时未交接。 +- 租客超时未归还。 +- 交接凭证争议。 +- 退款、押金、赔付争议。 + +证据要求: + +- 租前账号资产截图。 +- 租后账号资产截图。 +- 哈夫币数量截图。 +- 系统订单时间线。 +- 交接记录。 +- 站内沟通记录。 +- 第三方沟通截图。 +- 游戏内异常或封禁截图。 + +仲裁流程: + +1. 任一方在订单详情发起申诉。 +2. 订单进入申诉中,相关押金和结算金额保持冻结。 +3. 双方在限定时间内补充证据。 +4. 客服查看订单、交接、截图、聊天和资金流水。 +5. 客服选择裁决结果并填写裁决说明。 +6. 系统根据裁决结果调整订单状态和资金流水。 +7. 双方收到站内信,关键结果可短信通知。 + +裁决结果: + +- 全额退款。 +- 部分退款。 +- 释放押金。 +- 扣除部分押金。 +- 扣除全部押金。 +- 赔付号主。 +- 订单关闭。 +- 商品冻结。 +- 用户冻结。 +- 进入二次复核。 + +## 8. 消息通知机制 + +一期采用站内信加关键节点短信。 + +站内信场景: + +- 商品审核通过或拒绝。 +- 租客下单。 +- 订单进入待交接。 +- 号主提交交接说明。 +- 租客确认收号。 +- 租期即将结束。 +- 租客提交归还。 +- 号主确认归还。 +- 订单完成结算。 +- 发起申诉。 +- 仲裁结果。 +- 账户冻结或解冻。 + +短信场景: + +- 登录验证码。 +- 租期即将到期。 +- 订单申诉被受理。 +- 仲裁结果。 +- 高风险账号安全提醒。 + +扩展方向: + +- 后续可增加 WebSocket 或 SSE,实现订单状态和客服消息实时推送。 +- 短信必须做手机号、IP、设备、场景维度限流,防止短信轰炸。 + +## 9. 数据库设计概要 + +核心关系: + +- 一个用户可以同时是租客和号主。 +- 一个用户最多对应一条当前实名信息。 +- 一个号主可以发布多个游戏账号。 +- 一个游戏账号同一时间只能有一个上架商品或一个有效租赁订单。 +- 一个租赁商品关联一个游戏账号。 +- 一个订单关联一个租客、一个号主、一个商品和一个游戏账号快照。 +- 一个订单可以有多条交接记录、通知、审计日志和纠纷证据。 +- 资金流水只追加,不覆盖。 +- 后台管理员和角色为多对多关系,角色和权限为多对多关系。 +- 可调业务阈值统一放入 `system_configs`,例如最低押金、交接超时、归还超时、短信限流和实名下单开关。 + +核心表: + +`users` + +- `id` +- `phone` +- `nickname` +- `avatar_url` +- `realname_status` +- `risk_status` +- `credit_score` +- `status` +- `last_login_at` +- `created_at` +- `updated_at` + +`user_realname` + +- `id` +- `user_id` +- `provider` +- `provider_order_no` +- `status` +- `masked_name` +- `masked_id_no` +- `verified_at` +- `fail_reason` +- `created_at` +- `updated_at` + +`game_accounts` + +- `id` +- `owner_id` +- `game_name` +- `server_region` +- `login_platform` +- `title` +- `description` +- `rank_level` +- `haf_coin_amount` +- `asset_summary` +- `season_tags` +- `screenshot_urls` +- `status` +- `created_at` +- `updated_at` + +说明:`haf_coin_amount` 是号主最近一次填报值,不代表实时值;订单创建时必须写入 `rental_orders.account_snapshot`。 + +`rental_listings` + +- `id` +- `account_id` +- `owner_id` +- `price_hourly` +- `price_daily` +- `price_weekly` +- `deposit_amount` +- `min_rent_hours` +- `max_rent_hours` +- `status` +- `review_status` +- `review_reason` +- `published_at` +- `created_at` +- `updated_at` + +`rental_orders` + +- `id` +- `order_no` +- `listing_id` +- `account_id` +- `owner_id` +- `renter_id` +- `rent_start_at` +- `rent_end_at` +- `rent_hours` +- `rent_amount` +- `deposit_amount` +- `platform_fee` +- `account_snapshot` +- `status` +- `handoff_status` +- `settlement_status` +- `owner_settled_at` +- `settled_at` +- `created_at` +- `updated_at` + +`handoff_records` + +- `id` +- `order_id` +- `from_user_id` +- `to_user_id` +- `type` +- `content` +- `attachment_urls` +- `confirmed_by_renter_at` +- `confirmed_by_owner_at` +- `created_at` + +`wallet_accounts` + +- `id` +- `user_id` +- `available_balance` +- `frozen_balance` +- `status` +- `created_at` +- `updated_at` + +`wallet_ledger` + +- `id` +- `ledger_no` +- `user_id` +- `order_id` +- `direction` +- `amount` +- `balance_after` +- `balance_type` +- `biz_type` +- `biz_no` +- `remark` +- `created_at` + +说明:`balance_after` 记录本次流水发生后对应余额类型的余额,用于审计和对账。 + +`disputes` + +- `id` +- `order_id` +- `initiator_id` +- `target_user_id` +- `type` +- `status` +- `description` +- `evidence_urls` +- `arbitration_result` +- `arbitration_remark` +- `handled_by` +- `handled_at` +- `created_at` +- `updated_at` + +`notifications` + +- `id` +- `user_id` +- `type` +- `title` +- `content` +- `biz_type` +- `biz_id` +- `read_at` +- `created_at` + +`audit_logs` + +- `id` +- `actor_type` +- `actor_id` +- `action` +- `biz_type` +- `biz_id` +- `ip` +- `user_agent` +- `detail` +- `created_at` + +`admin_users` + +- `id` +- `username` +- `password_hash` +- `nickname` +- `status` +- `last_login_at` +- `created_at` +- `updated_at` + +`roles` + +- `id` +- `code` +- `name` +- `description` +- `created_at` +- `updated_at` + +`permissions` + +- `id` +- `code` +- `name` +- `resource` +- `action` +- `created_at` +- `updated_at` + +`admin_user_roles` + +- `id` +- `admin_user_id` +- `role_id` +- `created_at` + +`role_permissions` + +- `id` +- `role_id` +- `permission_id` +- `created_at` + +`system_configs` + +- `id` +- `key` +- `value` +- `description` +- `updated_by` +- `created_at` +- `updated_at` + +索引策略: + +- `users.phone` 唯一索引。 +- `game_accounts.owner_id` 普通索引。 +- `game_accounts.game_name, server_region, login_platform` 组合索引。 +- `rental_listings.status, review_status, price_hourly` 组合索引。 +- `rental_orders.order_no` 唯一索引。 +- `rental_orders.renter_id, status` 组合索引。 +- `rental_orders.owner_id, status` 组合索引。 +- `wallet_ledger.user_id, created_at` 组合索引。 +- `wallet_ledger.ledger_no` 唯一索引。 +- `disputes.order_id` 普通索引。 +- `notifications.user_id, read_at, created_at` 组合索引。 +- `audit_logs.actor_id, created_at` 组合索引。 +- `admin_user_roles.admin_user_id, role_id` 唯一索引。 +- `role_permissions.role_id, permission_id` 唯一索引。 +- `system_configs.key` 唯一索引。 + +## 10. API 接口规划 + +用户认证: + +- `POST /api/auth/sms/send`:发送短信验证码。 +- `POST /api/auth/sms/login`:手机号验证码登录。 +- `POST /api/auth/refresh`:刷新 Token。 +- `POST /api/auth/logout`:退出登录。 + +实名认证: + +- `POST /api/realname/start`:发起实名认证。 +- `GET /api/realname/status`:查询实名状态。 +- `POST /api/realname/callback`:实名认证服务回调。 + +租号发布: + +- `GET /api/listings`:租号列表。 +- `GET /api/listings/{id}`:租号详情。 +- `POST /api/listings`:创建发布。 +- `PUT /api/listings/{id}`:修改发布。 +- `POST /api/listings/{id}/submit-review`:提交审核。 +- `DELETE /api/listings/{id}`:下架发布。 + +订单: + +- `POST /api/orders`:创建订单。 +- `GET /api/orders`:订单列表。 +- `GET /api/orders/{id}`:订单详情。 +- `POST /api/orders/{id}/cancel`:取消订单。 +- `POST /api/orders/{id}/confirm-receive`:租客确认收号。 +- `POST /api/orders/{id}/return`:租客提交归还。 +- `POST /api/orders/{id}/confirm-return`:号主确认归还。 +- `POST /api/orders/{id}/dispute`:发起申诉。 + +交接: + +- `POST /api/orders/{id}/handoff`:号主提交交接说明。 +- `GET /api/orders/{id}/handoff-records`:查看交接记录。 + +钱包: + +- `GET /api/wallet/balance`:查询余额。 +- `GET /api/wallet/ledger`:查询流水。 +- `POST /api/wallet/withdraw`:提现申请预留接口。 + +通知: + +- `GET /api/notifications`:站内信列表。 +- `POST /api/notifications/{id}/read`:标记已读。 + +后台: + +- `GET /admin/dashboard`:后台仪表盘。 +- `GET /admin/users`:用户列表。 +- `POST /admin/users/{id}/freeze`:冻结用户。 +- `POST /admin/users/{id}/unfreeze`:解冻用户。 +- `GET /admin/listings/pending`:待审核商品。 +- `POST /admin/listings/{id}/approve`:审核通过。 +- `POST /admin/listings/{id}/reject`:审核拒绝。 +- `GET /admin/orders`:订单列表。 +- `GET /admin/disputes`:纠纷列表。 +- `POST /admin/orders/{id}/arbitrate`:订单仲裁。 +- `GET /admin/wallet/ledger`:资金流水。 +- `GET /admin/system-configs`:系统配置列表。 +- `PUT /admin/system-configs/{key}`:更新系统配置。 +- `GET /admin/audit-logs`:审计日志。 + +## 11. 安全与风控 + +认证安全: + +- 短信验证码存 Redis,设置过期时间。 +- 验证码一次性使用。 +- 限制手机号、IP、设备、场景维度发送频率。 +- 登录失败、验证码错误、异常设备需要计入风控。 + +接口安全: + +- 所有写接口校验 JWT。 +- 后台接口使用 Casbin 做 RBAC 权限控制。 +- 关键写操作防重复提交。 +- 金额、状态流转、订单归属必须由后端校验。 +- 所有 SQL 通过 ORM 参数化,禁止拼接用户输入。 + +业务风控: + +- 检测同一用户、同一设备、同一 IP 的异常注册和下单。 +- 检测号主自己租自己或关联账号刷单。 +- 检测重复发布同一账号。 +- 检测频繁取消、频繁申诉、频繁仲裁失败用户。 +- 检测高价值账号低押金异常发布。 +- 检测短时间大量短信请求。 + +账号安全: + +- 平台不保存游戏账号明文密码。 +- 交接说明和证据只对订单相关用户和管理员可见。 +- 敏感字段脱敏展示。 +- 实名信息加密存储。 +- 账号截图和纠纷证据使用私有文件访问,避免公开 URL 长期暴露。 + +审计要求: + +- 登录、实名、发布、审核、下单、交接、归还、申诉、仲裁、冻结、退款、结算都写审计日志。 +- 后台管理员的每次裁决必须记录操作者、时间、IP、说明和变更前后关键状态。 + +## 12. 后台管理 + +后台角色: + +- 超级管理员:拥有全部权限。 +- 审核员:处理商品审核和资料审核。 +- 客服:处理订单、交接问题和纠纷。 +- 财务:查看流水、处理结算和提现预留。 +- 风控:处理冻结、黑名单和异常用户。 + +后台功能: + +- 仪表盘:用户数、商品数、订单数、待审核、待仲裁、流水概览。 +- 用户管理:查看用户、实名状态、风险状态、冻结/解冻。 +- 商品审核:查看账号资产、截图、价格、押金、审核通过/拒绝。 +- 订单管理:查看订单时间线、交接记录、资金状态。 +- 仲裁中心:查看纠纷、证据、双方说明,执行裁决。 +- 资金流水:查看租金、押金、冻结、解冻、扣款、退款、结算。 +- 通知管理:查看站内信发送记录和短信发送记录。 +- 系统配置:管理交接超时、归还超时、短信限流、最低押金、平台抽成和实名下单开关。 +- 审计日志:查看管理员操作和高风险业务操作。 + +## 13. 前端页面与路由规划 + +用户端页面: + +- `/`:首页,展示推荐租号、热门区服和平台公告。 +- `/listings`:租号列表,支持区服、平台、价格、押金、段位、哈夫币数量筛选。 +- `/listings/:id`:租号详情,展示账号资产、租金、押金、租期、号主信用和风险提示。 +- `/orders/create`:下单页,选择租期并确认租金、押金和规则。 +- `/orders`:我的订单。 +- `/orders/:id`:订单详情,展示状态机时间线、交接记录、归还入口和申诉入口。 +- `/wallet`:钱包余额和流水。 +- `/notifications`:站内信。 +- `/realname`:实名认证。 + +号主端页面: + +- `/seller/listings`:我的发布。 +- `/seller/listings/create`:发布租号。 +- `/seller/listings/:id/edit`:编辑发布。 +- `/seller/handoffs`:交接管理。 +- `/seller/disputes`:纠纷处理。 +- `/seller/earnings`:收益流水。 + +后台端页面: + +- `/admin/dashboard`:仪表盘。 +- `/admin/users`:用户管理。 +- `/admin/listings/review`:商品审核。 +- `/admin/orders`:订单管理。 +- `/admin/disputes`:仲裁中心。 +- `/admin/wallet-ledger`:资金流水。 +- `/admin/system-configs`:系统配置。 +- `/admin/audit-logs`:审计日志。 + +前端实现原则: + +- 后台布局、菜单和权限控制可参考开源后台项目。 +- 租客端、号主端的交易体验和业务页面从头实现。 +- 路由权限需要区分游客、已登录用户、已实名用户、号主和管理员。 + +## 14. 项目目录规划 + +```text +hfb_sys/ +├── backend/ +│ ├── cmd/ +│ │ └── api/ +│ ├── config/ +│ ├── internal/ +│ │ ├── handler/ +│ │ ├── service/ +│ │ ├── repo/ +│ │ ├── model/ +│ │ ├── middleware/ +│ │ ├── modules/ +│ │ │ ├── auth/ +│ │ │ ├── user/ +│ │ │ ├── realname/ +│ │ │ ├── listing/ +│ │ │ ├── order/ +│ │ │ ├── wallet/ +│ │ │ ├── dispute/ +│ │ │ ├── notification/ +│ │ │ └── admin/ +│ │ └── integrations/ +│ │ ├── sms/ +│ │ ├── realname/ +│ │ └── storage/ +│ ├── pkg/ +│ └── migrations/ +├── frontend/ +│ ├── src/ +│ │ ├── api/ +│ │ ├── router/ +│ │ ├── stores/ +│ │ ├── views/ +│ │ ├── components/ +│ │ └── layouts/ +│ └── public/ +├── deploy/ +│ ├── docker-compose.dev.yml +│ ├── docker-compose.prod.yml +│ └── nginx/ +├── docs/ +│ ├── project-plan.md +│ ├── database.md +│ ├── api.md +│ └── business-rules.md +└── README.md +``` + +## 15. 阶段开发计划 + +第 0 阶段:项目初始化 + +- 创建前后端目录。 +- 初始化 Go 项目。 +- 初始化 Vue 项目。 +- 参考 `gin-vue-admin`、`go-admin` 的目录分层、RBAC、日志、配置、Swagger 和文件上传实现方式。 +- 配置 Docker Compose 开发环境。 +- 接入 MySQL、Redis、MinIO。 +- 建立基础配置、日志、错误码和响应结构。 + +第 1 阶段:用户与认证 + +- 实现短信验证码发送接口。 +- 实现短信验证码登录。 +- 实现 JWT 鉴权中间件。 +- 实现用户资料查询。 +- 加入短信限流和验证码一次性校验。 + +第 2 阶段:实名认证 + +- 建立实名表和状态流转。 +- 接入实名服务适配器。 +- 实现实名发起、查询、回调。 +- 限制未实名用户发布租号。 + +第 3 阶段:租号发布与审核 + +- 实现游戏账号和发布商品模型。 +- 实现哈夫币手动填报、区服隔离、赛季标签和账号资产截图。 +- 实现发布、修改、下架、提交审核。 +- 实现后台审核通过和拒绝。 +- 实现租号列表、详情和筛选。 + +第 4 阶段:订单、交接与账务 + +- 实现创建订单和账号锁定。 +- 实现订单状态机和合法状态转换校验。 +- 实现订单账号资产快照。 +- 实现号主提交交接说明。 +- 实现租客确认收号。 +- 实现租客提交归还、号主确认归还。 +- 实现交接、确认收号、归还、确认归还的超时处理。 +- 实现钱包账户、押金冻结、租金流水、结算流水。 +- 预留支付和提现接口。 + +第 5 阶段:纠纷、通知与后台 + +- 实现纠纷发起、举证、客服仲裁。 +- 实现站内信通知。 +- 接入关键节点短信通知。 +- 完善后台仪表盘、用户管理、订单管理、资金流水、系统配置、审计日志。 + +第 6 阶段:测试、风控与部署 + +- 补齐单元测试和集成测试。 +- 做并发下单测试。 +- 做越权、重复提交、短信轰炸基础安全测试。 +- 完成 Docker 生产部署配置。 +- 准备上线前用户协议、隐私政策、交易规则和风险提示。 + +## 16. 测试计划 + +文档检查: + +- `docs/project-plan.md` 是完整项目方案,不再是生成说明。 +- 文档明确写入借鉴开源、自研业务核心的路线。 +- 文档不把直接二开租号源码作为推荐方案。 +- 技术栈统一为 Go 方案,无 Java 或 Spring Boot 推荐残留。 +- MySQL 明确为主数据库。 +- Redis 明确用于验证码、缓存、锁和限流。 +- Docker 方案区分本地开发和生产部署。 +- 哈夫币边界明确,不描述为平台虚拟币。 +- 三角洲行动特有规则有独立章节。 +- 哈夫币数量明确为号主填报和订单快照,不承诺实时同步。 +- 一期账号交接明确为号主手动交接。 +- 交接超时阈值和处理策略明确。 +- 订单状态机有状态转换表。 +- 一期资金模型明确为先做账务,不直接接真实支付提现。 +- 一期通知明确为站内信加关键短信。 +- 数据库概要包含 `wallet_ledger.balance_after`、`system_configs`、`owner_settled_at`、`account_snapshot`、`admin_user_roles`、`role_permissions`。 +- 前端路由规划覆盖用户端、号主端和后台端。 + +功能测试: + +- 短信验证码正确、错误、过期、重复使用。 +- 手机号、IP、设备限流生效。 +- JWT 过期、伪造、缺失时被拒绝。 +- 未实名用户不能发布租号。 +- 实名认证成功、失败、认证中状态流转正确。 +- 商品草稿、待审核、已上架、已下架、审核拒绝状态流转正确。 +- 审核未通过商品不能下单。 +- 同一账号并发下单只能成功一单。 +- 订单待交接、租赁中、待归还确认、已完成、申诉中状态流转正确。 +- 非法订单状态转换会被拒绝。 +- 交接、收号、归还、确认归还超时会按配置触发取消、申诉、逾期或客服复核。 +- 押金冻结、释放、扣款、退款流水一致。 +- 每笔 `wallet_ledger` 正确记录 `balance_after`。 +- 租金、平台抽成、号主结算流水一致。 +- 订单结算后正确写入 `owner_settled_at` 或 `settled_at`。 +- 纠纷仲裁不同裁决结果正确落账。 +- 站内信和短信在关键节点触发。 + +安全测试: + +- 普通用户不能访问后台接口。 +- 后台不同角色只能访问授权功能。 +- 用户不能查看他人订单、钱包、交接记录或纠纷证据。 +- SQL 注入、越权访问、重复提交有基础防护。 +- 短信轰炸被限流。 +- 高风险操作全部写入审计日志。 + +性能与稳定性测试: + +- 租号列表分页和筛选在基础数据量下响应稳定。 +- 并发创建订单时库存锁定正确。 +- 钱包流水在并发结算时不出现负数或重复入账。 +- Redis、MySQL 短暂异常时接口返回可识别错误,不产生脏状态。 + +## 17. 风险与合规说明 + +账号租赁存在天然风险,包括账号找回、账号封禁、虚假描述、资产损失、租客恶意破坏、号主恶意交接、未成年人交易、资金纠纷和平台责任边界不清。 + +上线前必须补充: + +- 用户协议。 +- 隐私政策。 +- 租号交易规则。 +- 押金与赔付规则。 +- 纠纷仲裁规则。 +- 未成年人保护说明。 +- 实名认证授权说明。 +- 支付和提现合规方案。 +- 游戏官方规则风险评估。 + +平台规则必须明确: + +- 哈夫币不是平台发行资产。 +- 平台不保证游戏账号不会被官方限制、封禁或找回。 +- 平台不提供绕过游戏安全机制的技术服务。 +- 平台基于订单记录、交接记录、证据和仲裁规则处理争议。 +- 真实支付和提现上线前必须完成资质、风控、对账、发票或税务相关评估。 diff --git a/frontend/Dockerfile b/frontend/Dockerfile new file mode 100644 index 0000000..f4f0a4b --- /dev/null +++ b/frontend/Dockerfile @@ -0,0 +1,13 @@ +FROM node:24-alpine AS build + +WORKDIR /src +COPY frontend/package*.json ./ +RUN npm install +COPY frontend/ . +RUN npm run build + +FROM nginx:1.29-alpine + +COPY --from=build /src/dist /usr/share/nginx/html +COPY deploy/nginx/default.conf /etc/nginx/conf.d/default.conf +EXPOSE 80 diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..014872d --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,12 @@ + + + + + + 哈夫币租号平台 + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..e219384 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2201 @@ +{ + "name": "hfb-sys-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hfb-sys-frontend", + "version": "0.1.0", + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "axios": "^1.13.2", + "element-plus": "^2.11.9", + "pinia": "^3.0.4", + "vue": "^3.5.24", + "vue-router": "^4.6.3" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@vitejs/plugin-vue": "^6.0.2", + "@vue/tsconfig": "^0.8.1", + "typescript": "~5.9.3", + "vite": "^7.2.4", + "vue-tsc": "^3.1.5" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "24.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", + "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.7.tgz", + "integrity": "sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", + "integrity": "sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.28" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.28.tgz", + "integrity": "sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.28", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.28.tgz", + "integrity": "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.34.tgz", + "integrity": "sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/shared": "3.5.34", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz", + "integrity": "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz", + "integrity": "sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@vue/compiler-core": "3.5.34", + "@vue/compiler-dom": "3.5.34", + "@vue/compiler-ssr": "3.5.34", + "@vue/shared": "3.5.34", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.14", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.34.tgz", + "integrity": "sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/language-core": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-3.3.1.tgz", + "integrity": "sha512-NP8g6V7x81NVOXbLupUvYY6i6LqUkjkVowe2epRedmpgaFCOdjgWHE/rQBvEJ4r7koAYODIjGeBWEdt6n7jYXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.28", + "@vue/compiler-dom": "^3.5.0", + "@vue/shared": "^3.5.0", + "alien-signals": "^3.2.0", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1", + "picomatch": "^4.0.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.34.tgz", + "integrity": "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.34.tgz", + "integrity": "sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.34", + "@vue/shared": "3.5.34" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz", + "integrity": "sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.34", + "@vue/runtime-core": "3.5.34", + "@vue/shared": "3.5.34", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.34.tgz", + "integrity": "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.34", + "@vue/shared": "3.5.34" + }, + "peerDependencies": { + "vue": "3.5.34" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.34.tgz", + "integrity": "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==", + "license": "MIT" + }, + "node_modules/@vue/tsconfig": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@vue/tsconfig/-/tsconfig-0.8.1.tgz", + "integrity": "sha512-aK7feIWPXFSUhsCP9PFqPyFOcz4ENkb8hZ2pneL6m2UjCkccvaOhC/5KCKluuBufvp2KzkbdA2W2pk20vLzu3g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": "5.x", + "vue": "^3.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/@vueuse/core": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.3.0.tgz", + "integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.3.0", + "@vueuse/shared": "14.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.3.0.tgz", + "integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.3.0.tgz", + "integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/alien-signals": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-3.2.1.tgz", + "integrity": "sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", + "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/element-plus": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.14.0.tgz", + "integrity": "sha512-POgH+TtoreaEKWqYYAVQyE6i8rQMEFqAEublyF29dBA5yASWPLKY6EzfeqBTr2Uv26mPss4vSrMrNPyaK7LX5w==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.2.0", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.8", + "@types/lodash": "^4.17.24", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "14.3.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.20", + "lodash": "^4.18.1", + "lodash-es": "^4.18.1", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0", + "vue-component-type-helpers": "^3.2.8" + }, + "peerDependencies": { + "vue": "^3.3.7" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.3.tgz", + "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.34", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz", + "integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.34", + "@vue/compiler-sfc": "3.5.34", + "@vue/runtime-dom": "3.5.34", + "@vue/server-renderer": "3.5.34", + "@vue/shared": "3.5.34" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.1.tgz", + "integrity": "sha512-pu58kqxmVyEH6VfNYW1UyEfR3XAnJ27ZXT3yzXxxpjLxVzAbyC35Zk/nm/RMs7ijWnJNSd9fWkeex2OhUsx3MA==", + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-tsc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-3.3.1.tgz", + "integrity": "sha512-webBP3jhlxzhELZ2g+11KJ6pg5OVY1xWhWrj7N/yQMi1CrtxJnW+tUACyRVeDK0cQNLP2Va5HNYK8pe+7c+msw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.28", + "@vue/language-core": "3.3.1" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..5c62d9e --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,28 @@ +{ + "name": "hfb-sys-frontend", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "vue-tsc -b && vite build", + "preview": "vite preview --host 0.0.0.0", + "typecheck": "vue-tsc -b" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "axios": "^1.13.2", + "element-plus": "^2.11.9", + "pinia": "^3.0.4", + "vue": "^3.5.24", + "vue-router": "^4.6.3" + }, + "devDependencies": { + "@types/node": "^24.10.1", + "@vitejs/plugin-vue": "^6.0.2", + "@vue/tsconfig": "^0.8.1", + "typescript": "~5.9.3", + "vite": "^7.2.4", + "vue-tsc": "^3.1.5" + } +} diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..c51a1ce --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,11 @@ + + + diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts new file mode 100644 index 0000000..e1150a6 --- /dev/null +++ b/frontend/src/api/client.ts @@ -0,0 +1,6 @@ +import axios from 'axios' + +export const apiClient = axios.create({ + baseURL: '/api', + timeout: 10000, +}) diff --git a/frontend/src/layouts/AppLayout.vue b/frontend/src/layouts/AppLayout.vue new file mode 100644 index 0000000..ea47b5e --- /dev/null +++ b/frontend/src/layouts/AppLayout.vue @@ -0,0 +1,32 @@ + + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..3a64a0c --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,11 @@ +import 'element-plus/dist/index.css' +import './styles/base.css' + +import ElementPlus from 'element-plus' +import { createPinia } from 'pinia' +import { createApp } from 'vue' + +import App from './App.vue' +import router from './router' + +createApp(App).use(createPinia()).use(router).use(ElementPlus).mount('#app') diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 0000000..567ac8e --- /dev/null +++ b/frontend/src/router/index.ts @@ -0,0 +1,24 @@ +import { createRouter, createWebHistory } from 'vue-router' + +const router = createRouter({ + history: createWebHistory(), + routes: [ + { path: '/', name: 'home', component: () => import('@/views/public/HomeView.vue') }, + { path: '/listings', name: 'listings', component: () => import('@/views/public/ListingsView.vue') }, + { path: '/listings/:id', name: 'listing-detail', component: () => import('@/views/public/ListingDetailView.vue') }, + { path: '/orders/create', name: 'order-create', component: () => import('@/views/account/OrderCreateView.vue') }, + { path: '/orders', name: 'orders', component: () => import('@/views/account/OrdersView.vue') }, + { path: '/orders/:id', name: 'order-detail', component: () => import('@/views/account/OrderDetailView.vue') }, + { path: '/wallet', name: 'wallet', component: () => import('@/views/account/WalletView.vue') }, + { path: '/notifications', name: 'notifications', component: () => import('@/views/account/NotificationsView.vue') }, + { path: '/realname', name: 'realname', component: () => import('@/views/account/RealnameView.vue') }, + { path: '/seller/listings', name: 'seller-listings', component: () => import('@/views/seller/SellerListingsView.vue') }, + { path: '/seller/handoffs', name: 'seller-handoffs', component: () => import('@/views/seller/SellerHandoffsView.vue') }, + { path: '/seller/earnings', name: 'seller-earnings', component: () => import('@/views/seller/SellerEarningsView.vue') }, + { path: '/admin/dashboard', name: 'admin-dashboard', component: () => import('@/views/admin/AdminDashboardView.vue') }, + { path: '/admin/disputes', name: 'admin-disputes', component: () => import('@/views/admin/AdminDisputesView.vue') }, + { path: '/admin/system-configs', name: 'admin-system-configs', component: () => import('@/views/admin/AdminSystemConfigsView.vue') }, + ], +}) + +export default router diff --git a/frontend/src/stores/session.ts b/frontend/src/stores/session.ts new file mode 100644 index 0000000..1cd78e2 --- /dev/null +++ b/frontend/src/stores/session.ts @@ -0,0 +1,9 @@ +import { defineStore } from 'pinia' + +export const useSessionStore = defineStore('session', { + state: () => ({ + token: '', + phone: '', + realnameStatus: 'unknown', + }), +}) diff --git a/frontend/src/styles/base.css b/frontend/src/styles/base.css new file mode 100644 index 0000000..e285966 --- /dev/null +++ b/frontend/src/styles/base.css @@ -0,0 +1,164 @@ +:root { + color: #1f2933; + background: #f6f8fb; + font-family: + Inter, "PingFang SC", "Microsoft YaHei", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", + sans-serif; + font-synthesis: none; + text-rendering: optimizeLegibility; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-width: 320px; + min-height: 100vh; +} + +a { + color: inherit; + text-decoration: none; +} + +.app-shell { + display: grid; + min-height: 100vh; + grid-template-columns: 248px 1fr; +} + +.sidebar { + border-right: 1px solid #e4e7ed; + background: #ffffff; + padding: 20px 16px; +} + +.brand { + display: flex; + align-items: center; + gap: 10px; + min-height: 44px; + font-weight: 700; +} + +.brand-mark { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + border-radius: 8px; + background: #0f766e; + color: #ffffff; +} + +.nav { + display: grid; + gap: 6px; + margin-top: 24px; +} + +.nav-link { + display: flex; + align-items: center; + gap: 10px; + min-height: 40px; + border-radius: 8px; + padding: 0 12px; + color: #52616f; +} + +.nav-link.router-link-active { + background: #e8f5f2; + color: #0f766e; + font-weight: 600; +} + +.main { + min-width: 0; + padding: 32px; +} + +.page { + max-width: 1120px; +} + +.page-header { + max-width: 720px; +} + +.eyebrow { + margin: 0 0 8px; + color: #0f766e; + font-size: 13px; + font-weight: 700; + text-transform: uppercase; +} + +h1 { + margin: 0; + font-size: 32px; + line-height: 1.2; +} + +.page-header p:last-child { + margin: 12px 0 0; + color: #52616f; + line-height: 1.7; +} + +.metric-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; + margin-top: 28px; +} + +.metric-card { + border: 1px solid #e4e7ed; + border-radius: 8px; + background: #ffffff; + padding: 18px; +} + +.metric-card span { + display: block; + color: #6b7785; + font-size: 13px; +} + +.metric-card strong { + display: block; + margin-top: 8px; + font-size: 20px; +} + +@media (max-width: 760px) { + .app-shell { + grid-template-columns: 1fr; + } + + .sidebar { + position: sticky; + top: 0; + z-index: 10; + border-right: 0; + border-bottom: 1px solid #e4e7ed; + } + + .nav { + grid-auto-flow: column; + grid-auto-columns: max-content; + overflow-x: auto; + } + + .main { + padding: 24px 16px; + } + + .metric-grid { + grid-template-columns: 1fr; + } +} diff --git a/frontend/src/views/account/NotificationsView.vue b/frontend/src/views/account/NotificationsView.vue new file mode 100644 index 0000000..d7436a7 --- /dev/null +++ b/frontend/src/views/account/NotificationsView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/account/OrderCreateView.vue b/frontend/src/views/account/OrderCreateView.vue new file mode 100644 index 0000000..7067999 --- /dev/null +++ b/frontend/src/views/account/OrderCreateView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/account/OrderDetailView.vue b/frontend/src/views/account/OrderDetailView.vue new file mode 100644 index 0000000..f63835f --- /dev/null +++ b/frontend/src/views/account/OrderDetailView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/account/OrdersView.vue b/frontend/src/views/account/OrdersView.vue new file mode 100644 index 0000000..225d731 --- /dev/null +++ b/frontend/src/views/account/OrdersView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/account/RealnameView.vue b/frontend/src/views/account/RealnameView.vue new file mode 100644 index 0000000..86a2bca --- /dev/null +++ b/frontend/src/views/account/RealnameView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/account/WalletView.vue b/frontend/src/views/account/WalletView.vue new file mode 100644 index 0000000..dddc3e5 --- /dev/null +++ b/frontend/src/views/account/WalletView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/admin/AdminDashboardView.vue b/frontend/src/views/admin/AdminDashboardView.vue new file mode 100644 index 0000000..95354fb --- /dev/null +++ b/frontend/src/views/admin/AdminDashboardView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/admin/AdminDisputesView.vue b/frontend/src/views/admin/AdminDisputesView.vue new file mode 100644 index 0000000..9209c5f --- /dev/null +++ b/frontend/src/views/admin/AdminDisputesView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/admin/AdminSystemConfigsView.vue b/frontend/src/views/admin/AdminSystemConfigsView.vue new file mode 100644 index 0000000..d15bb45 --- /dev/null +++ b/frontend/src/views/admin/AdminSystemConfigsView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/public/HomeView.vue b/frontend/src/views/public/HomeView.vue new file mode 100644 index 0000000..a764006 --- /dev/null +++ b/frontend/src/views/public/HomeView.vue @@ -0,0 +1,23 @@ + diff --git a/frontend/src/views/public/ListingDetailView.vue b/frontend/src/views/public/ListingDetailView.vue new file mode 100644 index 0000000..2c4f593 --- /dev/null +++ b/frontend/src/views/public/ListingDetailView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/public/ListingsView.vue b/frontend/src/views/public/ListingsView.vue new file mode 100644 index 0000000..d725fcc --- /dev/null +++ b/frontend/src/views/public/ListingsView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/seller/SellerEarningsView.vue b/frontend/src/views/seller/SellerEarningsView.vue new file mode 100644 index 0000000..fbdc150 --- /dev/null +++ b/frontend/src/views/seller/SellerEarningsView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/seller/SellerHandoffsView.vue b/frontend/src/views/seller/SellerHandoffsView.vue new file mode 100644 index 0000000..69ea9fc --- /dev/null +++ b/frontend/src/views/seller/SellerHandoffsView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/src/views/seller/SellerListingsView.vue b/frontend/src/views/seller/SellerListingsView.vue new file mode 100644 index 0000000..6ba7e18 --- /dev/null +++ b/frontend/src/views/seller/SellerListingsView.vue @@ -0,0 +1,9 @@ + diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..15dd91b --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo", + "types": ["vite/client"], + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..ef37c7d --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "noEmit": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..0eb375e --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,19 @@ +import { fileURLToPath, URL } from 'node:url' + +import vue from '@vitejs/plugin-vue' +import { defineConfig } from 'vite' + +export default defineConfig({ + plugins: [vue()], + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)), + }, + }, + server: { + port: 5173, + proxy: { + '/api': 'http://127.0.0.1:8080', + }, + }, +})