u
This commit is contained in:
1 parent
c6671c8a8c
commit
f0cb963d23
76 files changed
+6467
-498
No files matched your search
Generated
+11
-3
@@ -4144,48 +4144,56 @@ packages:
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.27.0:
|
||||
resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.27.0:
|
||||
resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.27.0:
|
||||
resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.27.0:
|
||||
resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==}
|
||||
@@ -6279,7 +6287,7 @@ snapshots:
|
||||
|
||||
'@babel/generator@7.28.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.5
|
||||
'@babel/parser': 7.28.6
|
||||
'@babel/types': 7.28.6
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
@@ -6860,8 +6868,8 @@ snapshots:
|
||||
|
||||
'@babel/template@7.27.2':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.27.1
|
||||
'@babel/parser': 7.28.5
|
||||
'@babel/code-frame': 7.28.6
|
||||
'@babel/parser': 7.28.6
|
||||
'@babel/types': 7.28.6
|
||||
|
||||
'@babel/template@7.28.6':
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
_obj
|
||||
_test
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
_testmain.go
|
||||
*.exe
|
||||
*.exe~
|
||||
*.test
|
||||
*.prof
|
||||
*.rar
|
||||
*.zip
|
||||
*.gz
|
||||
*.psd
|
||||
*.bmd
|
||||
*.cfg
|
||||
*.pptx
|
||||
*.log
|
||||
*nohup.out
|
||||
*settings.pyc
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
!.gitkeep
|
||||
.DS_Store
|
||||
/.idea
|
||||
/.vscode
|
||||
/output
|
||||
*.local.yml
|
||||
dumped_hertz_remote_config.json
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"allapp-go/internal/bootstrap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app, err := bootstrap.NewApp()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
defer app.Close()
|
||||
|
||||
if err := bootstrap.RunServer(app.Config); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
app:
|
||||
port: 8087
|
||||
postgres:
|
||||
host: 117.72.182.135
|
||||
port: 5432
|
||||
user: postgres
|
||||
password: zhang520..
|
||||
dbname: app
|
||||
timezone: Asia/Shanghai
|
||||
max_open_conns: 10
|
||||
max_idle_conns: 5
|
||||
conn_max_lifetime: 30m
|
||||
sll_mode: false
|
||||
Whitespace-only changes.
@@ -0,0 +1,3 @@
|
||||
app:
|
||||
name: allapp
|
||||
port: 8080
|
||||
@@ -0,0 +1,51 @@
|
||||
module allapp-go
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/go-playground/validator/v10 v10.30.2
|
||||
github.com/gofiber/fiber/v3 v3.1.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/jackc/pgx/v5 v5.9.1
|
||||
github.com/spf13/viper v1.21.0
|
||||
go.uber.org/zap v1.27.1
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gofiber/schema v1.7.0 // indirect
|
||||
github.com/gofiber/utils/v2 v2.0.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/klauspost/compress v1.18.4 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/philhofer/fwd v1.2.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tinylib/msgp v1.6.3 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.69.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/crypto v0.49.0 // indirect
|
||||
golang.org/x/net v0.51.0 // indirect
|
||||
golang.org/x/sync v0.20.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/text v0.35.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,123 @@
|
||||
github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
|
||||
github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
|
||||
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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
|
||||
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
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.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ=
|
||||
github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/gofiber/fiber/v3 v3.1.0 h1:1p4I820pIa+FGxfwWuQZ5rAyX0WlGZbGT6Hnuxt6hKY=
|
||||
github.com/gofiber/fiber/v3 v3.1.0/go.mod h1:n2nYQovvL9z3Too/FGOfgtERjW3GQcAUqgfoezGBZdU=
|
||||
github.com/gofiber/schema v1.7.0 h1:yNM+FNRZjyYEli9Ey0AXRBrAY9jTnb+kmGs3lJGPvKg=
|
||||
github.com/gofiber/schema v1.7.0/go.mod h1:A/X5Ffyru4p9eBdp99qu+nzviHzQiZ7odLT+TwxWhbk=
|
||||
github.com/gofiber/utils/v2 v2.0.2 h1:ShRRssz0F3AhTlAQcuEj54OEDtWF7+HJDwEi/aa6QLI=
|
||||
github.com/gofiber/utils/v2 v2.0.2/go.mod h1:+9Ub4NqQ+IaJoTliq5LfdmOJAA/Hzwf4pXOxOa3RrJ0=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
|
||||
github.com/jackc/pgx/v5 v5.9.1/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c=
|
||||
github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
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-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
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/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/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||
github.com/shamaton/msgpack/v3 v3.1.0 h1:jsk0vEAqVvvS9+fTZ5/EcQ9tz860c9pWxJ4Iwecz8gU=
|
||||
github.com/shamaton/msgpack/v3 v3.1.0/go.mod h1:DcQG8jrdrQCIxr3HlMYkiXdMhK+KfN2CitkyzsQV4uc=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
|
||||
github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
|
||||
github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
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/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.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo=
|
||||
golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
|
||||
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
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=
|
||||
@@ -0,0 +1,58 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"allapp-go/internal/bootstrap/boots"
|
||||
"allapp-go/internal/config"
|
||||
"allapp-go/pkg/db"
|
||||
"allapp-go/pkg/logger"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type App struct {
|
||||
Config *config.Config
|
||||
PgSql *pgxpool.Pool
|
||||
}
|
||||
|
||||
// ======================
|
||||
// 初始化
|
||||
// ======================
|
||||
|
||||
func NewApp() (*App, error) {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 日志
|
||||
boots.InitLogger(cfg)
|
||||
|
||||
// 数据库
|
||||
pgsql, err := boots.InitPgsql(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db.Init(pgsql)
|
||||
|
||||
// 其他组件
|
||||
boots.InitUniqueId(cfg)
|
||||
boots.InitJwt(cfg)
|
||||
|
||||
return &App{
|
||||
Config: cfg,
|
||||
PgSql: pgsql,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ======================
|
||||
// 释放资源
|
||||
// ======================
|
||||
|
||||
func (a *App) Close() {
|
||||
if a.PgSql != nil {
|
||||
a.PgSql.Close()
|
||||
}
|
||||
|
||||
logger.Sync()
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package boots
|
||||
|
||||
import (
|
||||
"allapp-go/internal/config"
|
||||
"allapp-go/pkg/logger"
|
||||
)
|
||||
|
||||
func InitLogger(cfg *config.Config) {
|
||||
logger.Init(logger.Config{
|
||||
Dir: "logs",
|
||||
Level: "info",
|
||||
MaxSize: 100, // MB
|
||||
MaxBackups: 10,
|
||||
MaxAge: 7, // 天
|
||||
Compress: true,
|
||||
})
|
||||
defer logger.Sync()
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package boots
|
||||
|
||||
import (
|
||||
"allapp-go/internal/config"
|
||||
"allapp-go/pkg/jwtx"
|
||||
)
|
||||
|
||||
// InitJwt 初始化Jwt
|
||||
func InitJwt(cfg *config.Config) {
|
||||
jwtx.Init(cfg.JWT.Secret, cfg.JWT.AccessExpiry)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package boots
|
||||
|
||||
import (
|
||||
"allapp-go/internal/config"
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func InitPgsql(cfg *config.Config) (*pgxpool.Pool, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
pg := cfg.Postgres
|
||||
|
||||
// ssl
|
||||
sslMode := "disable"
|
||||
if pg.SslMode {
|
||||
sslMode = "require"
|
||||
}
|
||||
|
||||
// 密码转义
|
||||
password := url.QueryEscape(pg.Password)
|
||||
|
||||
// DSN
|
||||
dsn := fmt.Sprintf(
|
||||
"postgres://%s:%s@%s:%d/%s?sslmode=%s",
|
||||
pg.User,
|
||||
password,
|
||||
pg.Host,
|
||||
pg.Port,
|
||||
pg.Dbname,
|
||||
sslMode,
|
||||
)
|
||||
|
||||
// 解析配置
|
||||
conf, err := pgxpool.ParseConfig(dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// ======================
|
||||
// 连接池配置
|
||||
// ======================
|
||||
|
||||
// 最大连接数
|
||||
if pg.MaxOpenConns > 0 {
|
||||
conf.MaxConns = int32(pg.MaxOpenConns)
|
||||
}
|
||||
|
||||
// 最小连接数(近似 idle)
|
||||
if pg.MaxIdleConns > 0 {
|
||||
conf.MinConns = int32(pg.MaxIdleConns)
|
||||
}
|
||||
|
||||
// 连接最大生命周期
|
||||
if pg.ConnMaxLifetime > 0 {
|
||||
conf.MaxConnLifetime = pg.ConnMaxLifetime
|
||||
}
|
||||
|
||||
// ======================
|
||||
// 时区设置(正确方式 ⭐)
|
||||
// ======================
|
||||
if pg.TimeZone != "" {
|
||||
conf.ConnConfig.RuntimeParams["TimeZone"] = pg.TimeZone
|
||||
}
|
||||
|
||||
// 创建连接池
|
||||
db, err := pgxpool.NewWithConfig(ctx, conf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 测试连接
|
||||
if err := db.Ping(ctx); err != nil {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package boots
|
||||
|
||||
import (
|
||||
"allapp-go/internal/config"
|
||||
"allapp-go/pkg/uniqueid"
|
||||
)
|
||||
|
||||
// InitUniqueId 初始化雪花id
|
||||
func InitUniqueId(cfg *config.Config) {
|
||||
options := uniqueid.NewIdGeneratorOptions(cfg.UniqueID.WorkerID)
|
||||
uniqueid.SetIdGenerator(options)
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package bootstrap
|
||||
|
||||
import (
|
||||
"allapp-go/internal/config"
|
||||
"allapp-go/internal/errors"
|
||||
"allapp-go/internal/middleware"
|
||||
"allapp-go/internal/router"
|
||||
"allapp-go/pkg/logger"
|
||||
"fmt"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"github.com/gofiber/fiber/v3/middleware/recover"
|
||||
"github.com/gofiber/fiber/v3/middleware/requestid"
|
||||
)
|
||||
|
||||
func RunServer(cfg *config.Config) error {
|
||||
app := fiber.New(fiber.Config{
|
||||
ErrorHandler: errors.ErrorHandler(),
|
||||
})
|
||||
|
||||
// ===== middleware =====
|
||||
app.Use(recover.New())
|
||||
app.Use(requestid.New())
|
||||
app.Use(middleware.Logger())
|
||||
|
||||
// ===== router =====
|
||||
router.SetupRouter(app)
|
||||
|
||||
addr := fmt.Sprintf(":%d", cfg.App.Port)
|
||||
|
||||
logger.Log.Info("服务启动")
|
||||
|
||||
return app.Listen(addr)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
type Config struct {
|
||||
App AppConfig `mapstructure:"app"`
|
||||
JWT JwtConfig `mapstructure:"jwt"`
|
||||
UniqueID UniqueIDConfig `mapstructure:"unique_id"`
|
||||
Postgres PostgresConfig `mapstructure:"postgres"`
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Name string `mapstructure:"name"`
|
||||
Port int `mapstructure:"port"`
|
||||
}
|
||||
|
||||
type PostgresConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
Dbname string `mapstructure:"dbname"`
|
||||
SslMode bool `mapstructure:"ssl_mode"`
|
||||
TimeZone string `mapstructure:"timezone"`
|
||||
MaxOpenConns int32 `mapstructure:"max_open_conns"`
|
||||
MaxIdleConns int32 `mapstructure:"max_idle_conns"`
|
||||
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
|
||||
}
|
||||
|
||||
type JwtConfig struct {
|
||||
Secret string `mapstructure:"secret"`
|
||||
AccessExpiry time.Duration `mapstructure:"access_expiry"`
|
||||
}
|
||||
|
||||
type UniqueIDConfig struct {
|
||||
DataCenterID uint16 `mapstructure:"datacenter_id"`
|
||||
WorkerID uint16 `mapstructure:"worker_id"`
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
func Load() (*Config, error) {
|
||||
env := getEnv("APP_ENV", "dev")
|
||||
|
||||
v := viper.New()
|
||||
v.SetConfigType("yaml")
|
||||
|
||||
// ✅ 默认值
|
||||
setDefaults(v)
|
||||
|
||||
// ✅ 支持环境变量(如 APP_PORT → app.port)
|
||||
v.AutomaticEnv()
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
|
||||
// 基础配置
|
||||
v.AddConfigPath("configs")
|
||||
v.SetConfigName("config")
|
||||
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
return nil, fmt.Errorf("读取基础配置失败: %w", err)
|
||||
}
|
||||
|
||||
// 环境配置(覆盖)
|
||||
envConfigName := fmt.Sprintf("config.%s", env)
|
||||
v.SetConfigName(envConfigName)
|
||||
|
||||
if err := v.MergeInConfig(); err != nil {
|
||||
// dev 可以没有,prod 必须有
|
||||
if env == "prod" {
|
||||
return nil, fmt.Errorf("读取环境配置失败(%s): %w", env, err)
|
||||
}
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := v.Unmarshal(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("解析配置失败: %w", err)
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// ✅ 默认值统一在这里管理
|
||||
func setDefaults(v *viper.Viper) {
|
||||
v.SetDefault("app.name", "allapp-go")
|
||||
v.SetDefault("app.port", 8080)
|
||||
|
||||
// 可以继续扩展
|
||||
v.SetDefault("unique_id.datacenter_id", 1)
|
||||
v.SetDefault("unique_id.worker_id", 1)
|
||||
}
|
||||
|
||||
func getEnv(key, defaultVal string) string {
|
||||
val := os.Getenv(key)
|
||||
if val == "" {
|
||||
return defaultVal
|
||||
}
|
||||
return strings.ToLower(val)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package errors
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
|
||||
func WithStack(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"allapp-go/internal/httpx"
|
||||
"allapp-go/pkg/logger"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func ErrorHandler() fiber.ErrorHandler {
|
||||
return func(c fiber.Ctx, err error) error {
|
||||
code := fiber.StatusInternalServerError
|
||||
msg := "系统异常,请稍后再试!"
|
||||
|
||||
var e *fiber.Error
|
||||
if errors.As(err, &e) {
|
||||
code = e.Code
|
||||
msg = e.Message
|
||||
}
|
||||
|
||||
logger.FromCtx(c.Context()).Error("HTTP 请求异常",
|
||||
zap.Int("status", code),
|
||||
zap.String("error", fmt.Sprintf("%+v", err)),
|
||||
)
|
||||
|
||||
return httpx.SystemError(c, msg)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package handle
|
||||
|
||||
import (
|
||||
"allapp-go/internal/errors"
|
||||
"allapp-go/internal/httpx"
|
||||
"allapp-go/internal/types"
|
||||
"allapp-go/pkg/db"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
func LoadData(c fiber.Ctx) error {
|
||||
var req types.LoadDataReq
|
||||
|
||||
err := httpx.BindAndValidate(c, &req)
|
||||
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
dbClient := db.New()
|
||||
// 查询
|
||||
data, err := dbClient.LoadData(c.Context(), req.ViewName, req.SearchCondition, req.OrderBy, req.SearchColumns, req.Args)
|
||||
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return httpx.OK(c, data)
|
||||
}
|
||||
|
||||
func LoadDataBySql(c fiber.Ctx) error {
|
||||
var req types.LoadDataBySqlReq
|
||||
|
||||
err := httpx.BindAndValidate(c, &req)
|
||||
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
dbClient := db.New()
|
||||
|
||||
data, err := dbClient.LoadDataBySQL(c.Context(), req.Sql, req.Args)
|
||||
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return httpx.OK(c, data)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package handle
|
||||
|
||||
import (
|
||||
"allapp-go/internal/httpx"
|
||||
"allapp-go/internal/types"
|
||||
"allapp-go/pkg/db"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
func SaveData(c fiber.Ctx) error {
|
||||
var req []types.SaveDataReq
|
||||
|
||||
if err := httpx.BindAndValidate(c, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(req) == 0 {
|
||||
return httpx.OK(c, nil)
|
||||
}
|
||||
|
||||
ctx := c.Context()
|
||||
dbClient := db.New()
|
||||
|
||||
err := dbClient.WithTx(ctx, func(tx *db.Client) error {
|
||||
for _, dataReq := range req {
|
||||
tableName := dataReq.TableName
|
||||
keyField := dataReq.KeyField
|
||||
|
||||
for _, insert := range dataReq.Inserts {
|
||||
if err := tx.Insert(ctx, tableName, keyField, insert); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, update := range dataReq.Updates {
|
||||
if err := tx.Update(ctx, tableName, keyField, update); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, del := range dataReq.Deletes {
|
||||
if err := tx.Delete(ctx, tableName, keyField, del); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return httpx.OK(c, nil)
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package httpx
|
||||
|
||||
import (
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
var validate = validator.New()
|
||||
|
||||
func BindAndValidate(c fiber.Ctx, v any) error {
|
||||
if err := c.Bind().Body(v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := validate.Struct(v); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package httpx
|
||||
|
||||
import "github.com/gofiber/fiber/v3"
|
||||
|
||||
type Result struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data interface{} `json:"data"`
|
||||
TrackId string `json:"track_id,omitempty"`
|
||||
}
|
||||
|
||||
var BizCode = struct {
|
||||
Success int // 成功
|
||||
Unauthorized int // 未授权/Token失效
|
||||
BusinessError int // 业务异常,例如参数错误、操作不允许
|
||||
SystemError int // 系统异常,例如数据库崩溃、未知错误
|
||||
}{
|
||||
Success: 1000,
|
||||
Unauthorized: 1001,
|
||||
BusinessError: 2000,
|
||||
SystemError: 3000,
|
||||
}
|
||||
|
||||
// ------------------- 统一响应方法 -------------------
|
||||
|
||||
// Respond 返回通用响应,code和msg必填,data可选
|
||||
func Respond(c fiber.Ctx, code int, msg string, data interface{}) error {
|
||||
trackId := ""
|
||||
if v := c.Context().Value("request_id"); v != nil {
|
||||
if s, ok := v.(string); ok {
|
||||
trackId = s
|
||||
}
|
||||
}
|
||||
|
||||
return c.JSON(Result{
|
||||
Code: code,
|
||||
Message: msg,
|
||||
Data: data,
|
||||
TrackId: trackId,
|
||||
})
|
||||
}
|
||||
|
||||
// ------------------- 便捷封装 -------------------
|
||||
|
||||
// OK 成功响应
|
||||
func OK(c fiber.Ctx, data interface{}) error {
|
||||
return Respond(c, BizCode.Success, "success", data)
|
||||
}
|
||||
|
||||
// Fail 业务异常
|
||||
func Fail(c fiber.Ctx, msg string) error {
|
||||
return Respond(c, BizCode.BusinessError, msg, nil)
|
||||
}
|
||||
|
||||
// SystemError 系统异常
|
||||
func SystemError(c fiber.Ctx, msg string) error {
|
||||
return Respond(c, BizCode.SystemError, msg, nil)
|
||||
}
|
||||
|
||||
// Unauthorized 授权失败
|
||||
func Unauthorized(c fiber.Ctx, msg string) error {
|
||||
return Respond(c, BizCode.Unauthorized, msg, nil)
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"allapp-go/internal/httpx"
|
||||
"allapp-go/pkg/jwtx"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
const CtxUserIDKey = "user_id"
|
||||
|
||||
// Auth 登录校验中间件
|
||||
func Auth() fiber.Handler {
|
||||
return func(c fiber.Ctx) error {
|
||||
token := strings.TrimSpace(c.Get("Token"))
|
||||
|
||||
if token == "" {
|
||||
return httpx.Unauthorized(c, "未登录")
|
||||
}
|
||||
|
||||
verifyToken, ok := jwtx.VerifyToken(c.Context(), token)
|
||||
if !ok {
|
||||
return httpx.Unauthorized(c, "登录已过期")
|
||||
}
|
||||
|
||||
c.Locals(CtxUserIDKey, verifyToken.Data["user_id"])
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func GetUserID(c fiber.Ctx) (int64, error) {
|
||||
v := c.Locals(CtxUserIDKey)
|
||||
|
||||
if v == nil {
|
||||
return 0, httpx.Unauthorized(c, "登录已过期")
|
||||
}
|
||||
|
||||
userID, ok := v.(int64)
|
||||
|
||||
if !ok {
|
||||
return 0, httpx.Unauthorized(c, "登录已过期")
|
||||
}
|
||||
|
||||
return userID, nil
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"allapp-go/pkg/logger"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Logger() fiber.Handler {
|
||||
return func(c fiber.Ctx) error {
|
||||
traceID := c.RequestID()
|
||||
|
||||
logger.SetTraceID(c, traceID)
|
||||
|
||||
start := time.Now()
|
||||
|
||||
err := c.Next()
|
||||
|
||||
// ✅ 计算耗时
|
||||
cost := time.Since(start)
|
||||
|
||||
log := logger.FromCtx(c.Context())
|
||||
log.Info("HTTP 请求",
|
||||
zap.String("method", c.Method()),
|
||||
zap.String("path", c.Path()),
|
||||
zap.Int("status", c.Response().StatusCode()),
|
||||
zap.String("ip", c.IP()),
|
||||
zap.String("duration", cost.String()),
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"allapp-go/internal/handle"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
)
|
||||
|
||||
func SetupRouter(app *fiber.App) {
|
||||
// ==================== data ====================
|
||||
app.Post("/data/loadData", handle.LoadData)
|
||||
app.Post("/data/loadDataBySql", handle.LoadDataBySql)
|
||||
app.Post("/data/saveData", handle.SaveData)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package types
|
||||
|
||||
type LoadDataReq struct {
|
||||
ViewName string `json:"view_name" validate:"required"`
|
||||
SearchCondition string `json:"search_condition"`
|
||||
OrderBy string `json:"order_by"`
|
||||
SearchColumns []string `json:"search_columns"`
|
||||
Args []any `json:"args"`
|
||||
}
|
||||
|
||||
type LoadDataBySqlReq struct {
|
||||
Sql string `json:"sql" validate:"required"`
|
||||
Args []any `json:"args"`
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package types
|
||||
|
||||
type SaveDataReq struct {
|
||||
TableName string `json:"table_name"`
|
||||
KeyField string `json:"key_field"`
|
||||
Inserts []map[string]any `json:"inserts"`
|
||||
Updates []map[string]any `json:"updates"`
|
||||
Deletes []map[string]any `json:"deletes"`
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func validateData(table string, keyField string, data map[string]any) (any, error) {
|
||||
if table == "" {
|
||||
return nil, fmt.Errorf("表名不能为空")
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return nil, fmt.Errorf("数据不能为空")
|
||||
}
|
||||
|
||||
if keyField == "" {
|
||||
return nil, fmt.Errorf("主键字段不能为空")
|
||||
}
|
||||
|
||||
val, ok := data[keyField]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("缺少主键字段: %s", keyField)
|
||||
}
|
||||
|
||||
if val == nil {
|
||||
return nil, fmt.Errorf("主键字段值不能为空")
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
// 高效 placeholder 转换
|
||||
func convertPlaceholder(sql string) string {
|
||||
var sb strings.Builder
|
||||
sb.Grow(len(sql))
|
||||
|
||||
argIndex := 1
|
||||
|
||||
for i := 0; i < len(sql); i++ {
|
||||
if sql[i] == '?' {
|
||||
sb.WriteByte('$')
|
||||
sb.WriteString(strconv.Itoa(argIndex))
|
||||
argIndex++
|
||||
} else {
|
||||
sb.WriteByte(sql[i])
|
||||
}
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
var defaultClient *Client
|
||||
|
||||
type Executor interface {
|
||||
Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
|
||||
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
|
||||
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
pool *pgxpool.Pool
|
||||
tx pgx.Tx
|
||||
}
|
||||
|
||||
// Init 初始化
|
||||
func Init(pool *pgxpool.Pool) {
|
||||
defaultClient = &Client{pool: pool}
|
||||
}
|
||||
|
||||
// New 创建会话
|
||||
func New() *Client {
|
||||
return &Client{
|
||||
pool: defaultClient.pool,
|
||||
}
|
||||
}
|
||||
|
||||
// 内部获取执行器(关键)
|
||||
func (c *Client) exec() Executor {
|
||||
if c.tx != nil {
|
||||
return c.tx
|
||||
}
|
||||
return c.pool
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *Client) Delete(
|
||||
ctx context.Context,
|
||||
table string,
|
||||
keyField string,
|
||||
data map[string]any,
|
||||
) error {
|
||||
if keyField == "" {
|
||||
return fmt.Errorf("keyField 不能为空")
|
||||
}
|
||||
|
||||
keyVal, err := validateData(table, keyField, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sql := fmt.Sprintf(
|
||||
"DELETE FROM %s WHERE %s = $1",
|
||||
table,
|
||||
keyField,
|
||||
)
|
||||
|
||||
_, err = c.exec().Exec(ctx, sql, keyVal)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) BatchDelete(
|
||||
ctx context.Context,
|
||||
table string,
|
||||
keyField string,
|
||||
dataList []map[string]any,
|
||||
) error {
|
||||
|
||||
if table == "" {
|
||||
return fmt.Errorf("表名不能为空")
|
||||
}
|
||||
|
||||
if keyField == "" {
|
||||
return fmt.Errorf("主键字段不能为空")
|
||||
}
|
||||
|
||||
if len(dataList) == 0 {
|
||||
return fmt.Errorf("数据不能为空")
|
||||
}
|
||||
|
||||
var (
|
||||
placeholders []string
|
||||
args []any
|
||||
argIndex = 1
|
||||
)
|
||||
|
||||
for _, data := range dataList {
|
||||
keyVal, err := validateData(table, keyField, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if keyVal == nil {
|
||||
return fmt.Errorf("主键字段值不能为空")
|
||||
}
|
||||
|
||||
placeholders = append(placeholders, fmt.Sprintf("$%d", argIndex))
|
||||
args = append(args, keyVal)
|
||||
argIndex++
|
||||
}
|
||||
|
||||
sql := fmt.Sprintf(
|
||||
"DELETE FROM %s WHERE %s IN (%s)",
|
||||
table,
|
||||
keyField,
|
||||
strings.Join(placeholders, ", "),
|
||||
)
|
||||
|
||||
_, err := c.exec().Exec(ctx, sql, args...)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 构建 INSERT SQL(单条)
|
||||
func buildInsertSQL(table string, data map[string]any) (string, []any) {
|
||||
var (
|
||||
columns []string
|
||||
placeholders []string
|
||||
args []any
|
||||
)
|
||||
|
||||
i := 1
|
||||
for col, val := range data {
|
||||
columns = append(columns, col)
|
||||
placeholders = append(placeholders, fmt.Sprintf("$%d", i))
|
||||
args = append(args, val)
|
||||
i++
|
||||
}
|
||||
|
||||
sql := fmt.Sprintf(
|
||||
"INSERT INTO %s (%s) VALUES (%s)",
|
||||
table,
|
||||
strings.Join(columns, ", "),
|
||||
strings.Join(placeholders, ", "),
|
||||
)
|
||||
|
||||
return sql, args
|
||||
}
|
||||
|
||||
func (c *Client) Insert(
|
||||
ctx context.Context,
|
||||
table string,
|
||||
keyField string,
|
||||
data map[string]any,
|
||||
) error {
|
||||
if _, err := validateData(table, keyField, data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sql, args := buildInsertSQL(table, data)
|
||||
|
||||
_, err := c.exec().Exec(ctx, sql, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) BatchInsert(
|
||||
ctx context.Context,
|
||||
table string,
|
||||
keyField string,
|
||||
dataList []map[string]any,
|
||||
) error {
|
||||
if len(dataList) == 0 {
|
||||
return fmt.Errorf("数据不能为空")
|
||||
}
|
||||
|
||||
// 用第一条确定列顺序
|
||||
first := dataList[0]
|
||||
if _, err := validateData(table, keyField, first); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var columns []string
|
||||
for col := range first {
|
||||
columns = append(columns, col)
|
||||
}
|
||||
|
||||
sort.Strings(columns)
|
||||
|
||||
var (
|
||||
valueStrings []string
|
||||
args []any
|
||||
argIndex = 1
|
||||
)
|
||||
|
||||
for _, data := range dataList {
|
||||
// 统一校验
|
||||
if _, err := validateData(table, keyField, data); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 字段数量检查
|
||||
if len(data) != len(columns) {
|
||||
return fmt.Errorf("批量插入失败:数据字段不一致")
|
||||
}
|
||||
|
||||
var placeholders []string
|
||||
|
||||
for _, col := range columns {
|
||||
val, ok := data[col]
|
||||
if !ok {
|
||||
return fmt.Errorf("缺少字段: %s", col)
|
||||
}
|
||||
|
||||
placeholders = append(placeholders, fmt.Sprintf("$%d", argIndex))
|
||||
args = append(args, val)
|
||||
argIndex++
|
||||
}
|
||||
|
||||
valueStrings = append(
|
||||
valueStrings,
|
||||
fmt.Sprintf("(%s)", strings.Join(placeholders, ", ")),
|
||||
)
|
||||
}
|
||||
|
||||
sql := fmt.Sprintf(
|
||||
"INSERT INTO %s (%s) VALUES %s",
|
||||
table,
|
||||
strings.Join(columns, ", "),
|
||||
strings.Join(valueStrings, ", "),
|
||||
)
|
||||
|
||||
_, err := c.exec().Exec(ctx, sql, args...)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *Client) LoadData(
|
||||
ctx context.Context,
|
||||
viewName string,
|
||||
searchCondition string,
|
||||
orderBy string,
|
||||
searchColumns []string,
|
||||
args []any,
|
||||
) ([][]any, error) {
|
||||
|
||||
var where string
|
||||
|
||||
// WHERE 构造
|
||||
if searchCondition != "" && len(searchColumns) > 0 {
|
||||
var conditions []string
|
||||
|
||||
for _, col := range searchColumns {
|
||||
conditions = append(conditions, fmt.Sprintf("%s LIKE ?", col))
|
||||
}
|
||||
|
||||
where = " WHERE (" + strings.Join(conditions, " OR ") + ")"
|
||||
|
||||
// 自动加 %
|
||||
for i := range args {
|
||||
if s, ok := args[i].(string); ok {
|
||||
args[i] = "%" + s + "%"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ORDER BY
|
||||
var order string
|
||||
if orderBy != "" {
|
||||
order = " ORDER BY " + orderBy
|
||||
}
|
||||
|
||||
sql := fmt.Sprintf("SELECT * FROM %s%s%s", viewName, where, order)
|
||||
|
||||
return c.query(ctx, sql, args...)
|
||||
}
|
||||
|
||||
func (c *Client) LoadDataBySQL(
|
||||
ctx context.Context,
|
||||
sql string,
|
||||
args []any,
|
||||
) ([][]any, error) {
|
||||
return c.query(ctx, sql, args...)
|
||||
}
|
||||
|
||||
// 统一查询方法(核心优化)
|
||||
func (c *Client) query(ctx context.Context, sql string, args ...any) ([][]any, error) {
|
||||
sql = convertPlaceholder(sql)
|
||||
|
||||
rows, err := c.exec().Query(ctx, sql, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
result := make([][]any, 0, 16)
|
||||
|
||||
for rows.Next() {
|
||||
values, err := rows.Values()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
row := make([]any, len(values))
|
||||
copy(row, values)
|
||||
|
||||
result = append(result, row)
|
||||
}
|
||||
|
||||
return result, rows.Err()
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
// WithTx 开启事务(在当前 client 上)
|
||||
func (c *Client) WithTx(ctx context.Context, fn func(tx *Client) error) error {
|
||||
tx, err := c.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建事务 client
|
||||
txClient := &Client{
|
||||
pool: c.pool,
|
||||
tx: tx,
|
||||
}
|
||||
|
||||
defer func(tx pgx.Tx, ctx context.Context) {
|
||||
err := tx.Rollback(ctx)
|
||||
if err != nil {
|
||||
|
||||
}
|
||||
}(tx, ctx)
|
||||
|
||||
if err := fn(txClient); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (c *Client) Update(
|
||||
ctx context.Context,
|
||||
table string,
|
||||
keyField string,
|
||||
data map[string]any,
|
||||
) error {
|
||||
keyVal, err := validateData(table, keyField, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var (
|
||||
setClauses []string
|
||||
args []any
|
||||
i = 1
|
||||
)
|
||||
|
||||
for col, val := range data {
|
||||
if col == keyField {
|
||||
continue
|
||||
}
|
||||
setClauses = append(setClauses, fmt.Sprintf("%s=$%d", col, i))
|
||||
args = append(args, val)
|
||||
i++
|
||||
}
|
||||
|
||||
// WHERE 条件
|
||||
where := fmt.Sprintf("%s=$%d", keyField, i)
|
||||
args = append(args, keyVal)
|
||||
|
||||
sql := fmt.Sprintf(
|
||||
"UPDATE %s SET %s WHERE %s",
|
||||
table,
|
||||
strings.Join(setClauses, ", "),
|
||||
where,
|
||||
)
|
||||
|
||||
_, err = c.exec().Exec(ctx, sql, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) BatchUpdate(
|
||||
ctx context.Context,
|
||||
table string,
|
||||
keyField string,
|
||||
dataList []map[string]any,
|
||||
) error {
|
||||
|
||||
if len(dataList) == 0 {
|
||||
return fmt.Errorf("数据不能为空")
|
||||
}
|
||||
|
||||
// 用第一条数据确定字段
|
||||
first := dataList[0]
|
||||
if _, err := validateData(table, keyField, first); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 提取字段(排除主键)+ 排序(关键)
|
||||
var columns []string
|
||||
for col := range first {
|
||||
if col != keyField {
|
||||
columns = append(columns, col)
|
||||
}
|
||||
}
|
||||
sort.Strings(columns)
|
||||
|
||||
var (
|
||||
args []any
|
||||
argIndex = 1
|
||||
)
|
||||
|
||||
// CASE 语句
|
||||
var setClauses []string
|
||||
|
||||
for _, col := range columns {
|
||||
var caseBuilder strings.Builder
|
||||
|
||||
caseBuilder.WriteString(fmt.Sprintf("%s = CASE %s ", col, keyField))
|
||||
|
||||
for _, data := range dataList {
|
||||
// 校验 key
|
||||
keyVal, err := validateData(table, keyField, data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val, ok := data[col]
|
||||
if !ok {
|
||||
return fmt.Errorf("缺少字段: %s", col)
|
||||
}
|
||||
|
||||
caseBuilder.WriteString(fmt.Sprintf(
|
||||
"WHEN $%d THEN $%d ",
|
||||
argIndex,
|
||||
argIndex+1,
|
||||
))
|
||||
|
||||
args = append(args, keyVal, val)
|
||||
argIndex += 2
|
||||
}
|
||||
|
||||
caseBuilder.WriteString("END")
|
||||
setClauses = append(setClauses, caseBuilder.String())
|
||||
}
|
||||
|
||||
// WHERE IN
|
||||
var wherePlaceholders []string
|
||||
|
||||
for _, data := range dataList {
|
||||
keyVal, ok := data[keyField]
|
||||
if !ok || keyVal == nil {
|
||||
return fmt.Errorf("缺少主键字段值: %s", keyField)
|
||||
}
|
||||
|
||||
wherePlaceholders = append(wherePlaceholders, fmt.Sprintf("$%d", argIndex))
|
||||
args = append(args, keyVal)
|
||||
argIndex++
|
||||
}
|
||||
|
||||
sql := fmt.Sprintf(
|
||||
"UPDATE %s SET %s WHERE %s IN (%s)",
|
||||
table,
|
||||
strings.Join(setClauses, ", "),
|
||||
keyField,
|
||||
strings.Join(wherePlaceholders, ", "),
|
||||
)
|
||||
|
||||
_, err := c.exec().Exec(ctx, sql, args...)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package jwtx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
// ==================
|
||||
// 内部管理器
|
||||
// ==================
|
||||
|
||||
type jwtManager struct {
|
||||
secret string
|
||||
tokenExpiry time.Duration
|
||||
}
|
||||
|
||||
var (
|
||||
manager *jwtManager
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// ==================
|
||||
// 初始化
|
||||
// ==================
|
||||
|
||||
// Init 初始化(只执行一次)
|
||||
// expiry = -1 表示不过期
|
||||
func Init(secret string, expiry time.Duration) {
|
||||
once.Do(func() {
|
||||
manager = &jwtManager{
|
||||
secret: secret,
|
||||
tokenExpiry: expiry,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func getManager() *jwtManager {
|
||||
if manager == nil {
|
||||
panic("jwt未初始化,请先调用Init")
|
||||
}
|
||||
return manager
|
||||
}
|
||||
|
||||
// ==================
|
||||
// Claims(通用结构)
|
||||
// ==================
|
||||
|
||||
type CustomClaims struct {
|
||||
Data map[string]any `json:"data"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
// ==================
|
||||
// 对外方法
|
||||
// ==================
|
||||
|
||||
// CreateToken 生成 JWT
|
||||
func CreateToken(ctx context.Context, data map[string]any) (string, error) {
|
||||
m := getManager()
|
||||
return m.createToken(data)
|
||||
}
|
||||
|
||||
// VerifyToken 校验 token 并返回 claims
|
||||
func VerifyToken(ctx context.Context, tokenString string) (*CustomClaims, bool) {
|
||||
if tokenString == "" {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
m := getManager()
|
||||
|
||||
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(m.secret), nil
|
||||
})
|
||||
|
||||
if err != nil || !token.Valid {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(*CustomClaims)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return claims, true
|
||||
}
|
||||
|
||||
// ==================
|
||||
// 内部实现
|
||||
// ==================
|
||||
|
||||
func (j *jwtManager) createToken(data map[string]any) (string, error) {
|
||||
now := time.Now()
|
||||
|
||||
claims := &CustomClaims{
|
||||
Data: data,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
},
|
||||
}
|
||||
|
||||
// ✅ 只有 >0 才设置过期时间
|
||||
// -1 / 0 都表示不过期
|
||||
if j.tokenExpiry > 0 {
|
||||
claims.ExpiresAt = jwt.NewNumericDate(now.Add(j.tokenExpiry))
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(j.secret))
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v3"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
var Log *zap.Logger
|
||||
|
||||
const TraceIDKey = "request_id"
|
||||
|
||||
// ======================
|
||||
// 配置结构
|
||||
// ======================
|
||||
|
||||
type Config struct {
|
||||
Dir string // 日志目录
|
||||
Level string // debug info warn error
|
||||
MaxSize int // MB
|
||||
MaxBackups int
|
||||
MaxAge int // 天
|
||||
Compress bool // 是否压缩
|
||||
}
|
||||
|
||||
// ======================
|
||||
// 初始化
|
||||
// ======================
|
||||
|
||||
func Init(cfg Config) {
|
||||
if cfg.Dir == "" {
|
||||
cfg.Dir = "logs"
|
||||
}
|
||||
|
||||
if cfg.Level == "" {
|
||||
cfg.Level = "info"
|
||||
}
|
||||
|
||||
level := parseLevel(cfg.Level)
|
||||
|
||||
// ======================
|
||||
// encoder 配置
|
||||
// ======================
|
||||
|
||||
encoderConfig := zapcore.EncoderConfig{
|
||||
TimeKey: "time",
|
||||
LevelKey: "level",
|
||||
MessageKey: "msg",
|
||||
CallerKey: "caller",
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
}
|
||||
|
||||
// 👉 文件 JSON encoder
|
||||
jsonEncoder := zapcore.NewJSONEncoder(func() zapcore.EncoderConfig {
|
||||
cfg := encoderConfig
|
||||
cfg.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
|
||||
return cfg
|
||||
}())
|
||||
|
||||
// 👉 控制台 encoder(可读)
|
||||
consoleEncoder := zapcore.NewConsoleEncoder(func() zapcore.EncoderConfig {
|
||||
cfg := encoderConfig
|
||||
cfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
|
||||
cfg.EncodeTime = zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05")
|
||||
return cfg
|
||||
}())
|
||||
|
||||
// ======================
|
||||
// writer
|
||||
// ======================
|
||||
|
||||
consoleWriter := zapcore.AddSync(os.Stdout)
|
||||
infoWriter := getWriter(cfg, "info")
|
||||
errorWriter := getWriter(cfg, "error")
|
||||
|
||||
// ======================
|
||||
// core
|
||||
// ======================
|
||||
|
||||
// 控制台(所有级别)
|
||||
consoleCore := zapcore.NewCore(
|
||||
consoleEncoder,
|
||||
consoleWriter,
|
||||
level,
|
||||
)
|
||||
|
||||
// info 文件
|
||||
infoCore := zapcore.NewCore(
|
||||
jsonEncoder,
|
||||
infoWriter,
|
||||
zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl < zapcore.ErrorLevel && lvl >= level
|
||||
}),
|
||||
)
|
||||
|
||||
// error 文件
|
||||
errorCore := zapcore.NewCore(
|
||||
jsonEncoder,
|
||||
errorWriter,
|
||||
zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl >= zapcore.ErrorLevel
|
||||
}),
|
||||
)
|
||||
|
||||
core := zapcore.NewTee(
|
||||
consoleCore,
|
||||
infoCore,
|
||||
errorCore,
|
||||
)
|
||||
|
||||
Log = zap.New(
|
||||
core,
|
||||
zap.AddCaller(), // 打印调用位置
|
||||
zap.AddCallerSkip(1), // 跳过封装层
|
||||
)
|
||||
}
|
||||
|
||||
// ======================
|
||||
// Daily Writer(按天切割)
|
||||
// ======================
|
||||
|
||||
type DailyWriter struct {
|
||||
cfg Config
|
||||
level string
|
||||
log *lumberjack.Logger
|
||||
date string
|
||||
}
|
||||
|
||||
func newDailyWriter(cfg Config, level string) *DailyWriter {
|
||||
return &DailyWriter{
|
||||
cfg: cfg,
|
||||
level: level,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *DailyWriter) Write(p []byte) (n int, err error) {
|
||||
today := time.Now().Format("2006-01-02")
|
||||
|
||||
if w.log == nil || w.date != today {
|
||||
dir := filepath.Join(w.cfg.Dir, w.level)
|
||||
_ = os.MkdirAll(dir, os.ModePerm)
|
||||
|
||||
filename := filepath.Join(dir, today+".log")
|
||||
|
||||
w.log = &lumberjack.Logger{
|
||||
Filename: filename,
|
||||
MaxSize: w.cfg.MaxSize,
|
||||
MaxBackups: w.cfg.MaxBackups,
|
||||
MaxAge: w.cfg.MaxAge,
|
||||
Compress: w.cfg.Compress,
|
||||
}
|
||||
|
||||
w.date = today
|
||||
}
|
||||
|
||||
return w.log.Write(p)
|
||||
}
|
||||
|
||||
func (w *DailyWriter) Sync() error {
|
||||
if w.log != nil {
|
||||
return w.log.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ======================
|
||||
// writer 封装
|
||||
// ======================
|
||||
|
||||
func getWriter(cfg Config, level string) zapcore.WriteSyncer {
|
||||
return zapcore.AddSync(newDailyWriter(cfg, level))
|
||||
}
|
||||
|
||||
// ======================
|
||||
// level 解析
|
||||
// ======================
|
||||
|
||||
func parseLevel(lvl string) zapcore.Level {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "debug":
|
||||
return zap.DebugLevel
|
||||
case "warn":
|
||||
return zap.WarnLevel
|
||||
case "error":
|
||||
return zap.ErrorLevel
|
||||
default:
|
||||
return zap.InfoLevel
|
||||
}
|
||||
}
|
||||
|
||||
// ======================
|
||||
// trace 支持
|
||||
// ======================
|
||||
|
||||
func SetTraceID(c fiber.Ctx, traceID string) {
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(c.Context(), TraceIDKey, traceID)
|
||||
c.SetContext(ctx)
|
||||
}
|
||||
|
||||
func FromCtx(ctx context.Context) *zap.Logger {
|
||||
if ctx == nil {
|
||||
return Log
|
||||
}
|
||||
|
||||
traceID, _ := ctx.Value(TraceIDKey).(string)
|
||||
if traceID == "" {
|
||||
return Log
|
||||
}
|
||||
|
||||
return Log.With(zap.String("trace_id", traceID))
|
||||
}
|
||||
|
||||
// ======================
|
||||
// 优雅退出
|
||||
// ======================
|
||||
|
||||
func Sync() {
|
||||
if Log != nil {
|
||||
_ = Log.Sync()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package uniqueid
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type DefaultIdGenerator struct {
|
||||
Options *IdGeneratorOptions
|
||||
SnowWorker ISnowWorker
|
||||
IdGeneratorException IdGeneratorException
|
||||
}
|
||||
|
||||
func NewDefaultIdGenerator(options *IdGeneratorOptions) *DefaultIdGenerator {
|
||||
if options == nil {
|
||||
panic("dig.Options error.")
|
||||
}
|
||||
|
||||
// 1.BaseTime
|
||||
minTime := int64(631123200000) // time.Now().AddDate(-30, 0, 0).UnixNano() / 1e6
|
||||
if options.BaseTime < minTime || options.BaseTime > time.Now().UnixNano()/1e6 {
|
||||
panic("BaseTime error.")
|
||||
}
|
||||
|
||||
// 2.WorkerIdBitLength
|
||||
if options.WorkerIdBitLength <= 0 {
|
||||
panic("WorkerIdBitLength error.(range:[1, 21])")
|
||||
}
|
||||
if options.WorkerIdBitLength+options.SeqBitLength > 22 {
|
||||
panic("error:WorkerIdBitLength + SeqBitLength <= 22")
|
||||
}
|
||||
|
||||
// 3.WorkerId
|
||||
maxWorkerIdNumber := uint16(1<<options.WorkerIdBitLength) - 1
|
||||
if maxWorkerIdNumber == 0 {
|
||||
maxWorkerIdNumber = 63
|
||||
}
|
||||
if options.WorkerId < 0 || options.WorkerId > maxWorkerIdNumber {
|
||||
panic("WorkerId error. (range:[0, " + strconv.FormatUint(uint64(maxWorkerIdNumber), 10) + "]")
|
||||
}
|
||||
|
||||
// 4.SeqBitLength
|
||||
if options.SeqBitLength < 2 || options.SeqBitLength > 21 {
|
||||
panic("SeqBitLength error. (range:[2, 21])")
|
||||
}
|
||||
|
||||
// 5.MaxSeqNumber
|
||||
maxSeqNumber := uint32(1<<options.SeqBitLength) - 1
|
||||
if maxSeqNumber == 0 {
|
||||
maxSeqNumber = 63
|
||||
}
|
||||
if options.MaxSeqNumber < 0 || options.MaxSeqNumber > maxSeqNumber {
|
||||
panic("MaxSeqNumber error. (range:[1, " + strconv.FormatUint(uint64(maxSeqNumber), 10) + "]")
|
||||
}
|
||||
|
||||
// 6.MinSeqNumber
|
||||
if options.MinSeqNumber < 5 || options.MinSeqNumber > maxSeqNumber {
|
||||
panic("MinSeqNumber error. (range:[5, " + strconv.FormatUint(uint64(maxSeqNumber), 10) + "]")
|
||||
}
|
||||
|
||||
var snowWorker ISnowWorker
|
||||
switch options.Method {
|
||||
case 1:
|
||||
snowWorker = NewSnowWorkerM1(options)
|
||||
case 2:
|
||||
snowWorker = NewSnowWorkerM2(options)
|
||||
default:
|
||||
snowWorker = NewSnowWorkerM1(options)
|
||||
}
|
||||
|
||||
if options.Method == 1 {
|
||||
time.Sleep(time.Duration(500) * time.Microsecond)
|
||||
}
|
||||
|
||||
return &DefaultIdGenerator{
|
||||
Options: options,
|
||||
SnowWorker: snowWorker,
|
||||
}
|
||||
}
|
||||
|
||||
func (dig DefaultIdGenerator) NewLong() int64 {
|
||||
return dig.SnowWorker.NextId()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package uniqueid
|
||||
|
||||
type IIdGenerator interface {
|
||||
NewLong() uint64
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package uniqueid
|
||||
|
||||
type ISnowWorker interface {
|
||||
NextId() int64
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package uniqueid
|
||||
|
||||
import "fmt"
|
||||
|
||||
type IdGeneratorException struct {
|
||||
message string
|
||||
error error
|
||||
}
|
||||
|
||||
func (e IdGeneratorException) IdGeneratorException(message ...interface{}) {
|
||||
fmt.Println(message)
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package uniqueid
|
||||
|
||||
type IdGeneratorOptions struct {
|
||||
Method uint16 // 雪花计算方法,(1-漂移算法|2-传统算法),默认1
|
||||
BaseTime int64 // 基础时间(ms单位),不能超过当前系统时间
|
||||
WorkerId uint16 // 机器码,必须由外部设定,最大值 2^WorkerIdBitLength-1
|
||||
WorkerIdBitLength byte // 机器码位长,默认值6,取值范围 [1, 15](要求:序列数位长+机器码位长不超过22)
|
||||
SeqBitLength byte // 序列数位长,默认值6,取值范围 [3, 21](要求:序列数位长+机器码位长不超过22)
|
||||
MaxSeqNumber uint32 // 最大序列数(含),设置范围 [MinSeqNumber, 2^SeqBitLength-1],默认值0,表示最大序列数取最大值(2^SeqBitLength-1])
|
||||
MinSeqNumber uint32 // 最小序列数(含),默认值5,取值范围 [5, MaxSeqNumber],每毫秒的前5个序列数对应编号0-4是保留位,其中1-4是时间回拨相应预留位,0是手工新值预留位
|
||||
TopOverCostCount uint32 // 最大漂移次数(含),默认2000,推荐范围500-10000(与计算能力有关)
|
||||
}
|
||||
|
||||
func NewIdGeneratorOptions(workerId uint16) *IdGeneratorOptions {
|
||||
return &IdGeneratorOptions{
|
||||
Method: 1,
|
||||
WorkerId: workerId,
|
||||
BaseTime: 1582136402000,
|
||||
WorkerIdBitLength: 6,
|
||||
SeqBitLength: 6,
|
||||
MaxSeqNumber: 0,
|
||||
MinSeqNumber: 5,
|
||||
TopOverCostCount: 2000,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package uniqueid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
var singletonMutex sync.Mutex
|
||||
var idGenerator *DefaultIdGenerator
|
||||
|
||||
// SetIdGenerator .
|
||||
func SetIdGenerator(options *IdGeneratorOptions) {
|
||||
singletonMutex.Lock()
|
||||
idGenerator = NewDefaultIdGenerator(options)
|
||||
singletonMutex.Unlock()
|
||||
}
|
||||
|
||||
// NextId .
|
||||
func NextId() int64 {
|
||||
if idGenerator == nil {
|
||||
singletonMutex.Lock()
|
||||
defer singletonMutex.Unlock()
|
||||
if idGenerator == nil {
|
||||
options := NewIdGeneratorOptions(1)
|
||||
idGenerator = NewDefaultIdGenerator(options)
|
||||
}
|
||||
}
|
||||
|
||||
return idGenerator.NewLong()
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package uniqueid
|
||||
|
||||
type OverCostActionArg struct {
|
||||
ActionType int32
|
||||
TimeTick int64
|
||||
WorkerId uint16
|
||||
OverCostCountInOneTerm int32
|
||||
GenCountInOneTerm int32
|
||||
TermIndex int32
|
||||
}
|
||||
|
||||
func (ocaa OverCostActionArg) OverCostActionArg(workerId uint16, timeTick int64, actionType int32, overCostCountInOneTerm int32, genCountWhenOverCost int32, index int32) {
|
||||
ocaa.ActionType = actionType
|
||||
ocaa.TimeTick = timeTick
|
||||
ocaa.WorkerId = workerId
|
||||
ocaa.OverCostCountInOneTerm = overCostCountInOneTerm
|
||||
ocaa.GenCountInOneTerm = genCountWhenOverCost
|
||||
ocaa.TermIndex = index
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package uniqueid
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// SnowWorkerM1 .
|
||||
type SnowWorkerM1 struct {
|
||||
BaseTime int64 //基础时间
|
||||
WorkerId uint16 //机器码
|
||||
WorkerIdBitLength byte //机器码位长
|
||||
SeqBitLength byte //自增序列数位长
|
||||
MaxSeqNumber uint32 //最大序列数(含)
|
||||
MinSeqNumber uint32 //最小序列数(含)
|
||||
TopOverCostCount uint32 //最大漂移次数
|
||||
_TimestampShift byte
|
||||
_CurrentSeqNumber uint32
|
||||
|
||||
_LastTimeTick int64
|
||||
_TurnBackTimeTick int64
|
||||
_TurnBackIndex byte
|
||||
_IsOverCost bool
|
||||
_OverCostCountInOneTerm uint32
|
||||
_GenCountInOneTerm uint32
|
||||
_TermIndex uint32
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewSnowWorkerM1 .
|
||||
func NewSnowWorkerM1(options *IdGeneratorOptions) ISnowWorker {
|
||||
var workerIdBitLength byte
|
||||
var seqBitLength byte
|
||||
var maxSeqNumber uint32
|
||||
|
||||
// 1.BaseTime
|
||||
var baseTime int64
|
||||
if options.BaseTime != 0 {
|
||||
baseTime = options.BaseTime
|
||||
} else {
|
||||
baseTime = 1582136402000
|
||||
}
|
||||
|
||||
// 2.WorkerIdBitLength
|
||||
if options.WorkerIdBitLength == 0 {
|
||||
workerIdBitLength = 6
|
||||
} else {
|
||||
workerIdBitLength = options.WorkerIdBitLength
|
||||
}
|
||||
|
||||
// 3.WorkerId
|
||||
var workerId = options.WorkerId
|
||||
|
||||
// 4.SeqBitLength
|
||||
if options.SeqBitLength == 0 {
|
||||
seqBitLength = 6
|
||||
} else {
|
||||
seqBitLength = options.SeqBitLength
|
||||
}
|
||||
|
||||
// 5.MaxSeqNumber
|
||||
if options.MaxSeqNumber <= 0 {
|
||||
maxSeqNumber = (1 << seqBitLength) - 1
|
||||
} else {
|
||||
maxSeqNumber = options.MaxSeqNumber
|
||||
}
|
||||
|
||||
// 6.MinSeqNumber
|
||||
var minSeqNumber = options.MinSeqNumber
|
||||
|
||||
// 7.Others
|
||||
var topOverCostCount = options.TopOverCostCount
|
||||
if topOverCostCount == 0 {
|
||||
topOverCostCount = 2000
|
||||
}
|
||||
|
||||
timestampShift := (byte)(workerIdBitLength + seqBitLength)
|
||||
currentSeqNumber := minSeqNumber
|
||||
|
||||
return &SnowWorkerM1{
|
||||
BaseTime: baseTime,
|
||||
WorkerIdBitLength: workerIdBitLength,
|
||||
WorkerId: workerId,
|
||||
SeqBitLength: seqBitLength,
|
||||
MaxSeqNumber: maxSeqNumber,
|
||||
MinSeqNumber: minSeqNumber,
|
||||
TopOverCostCount: topOverCostCount,
|
||||
_TimestampShift: timestampShift,
|
||||
_CurrentSeqNumber: currentSeqNumber,
|
||||
|
||||
_LastTimeTick: 0,
|
||||
_TurnBackTimeTick: 0,
|
||||
_TurnBackIndex: 0,
|
||||
_IsOverCost: false,
|
||||
_OverCostCountInOneTerm: 0,
|
||||
_GenCountInOneTerm: 0,
|
||||
_TermIndex: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// DoGenIDAction .
|
||||
func (m1 *SnowWorkerM1) DoGenIdAction(arg *OverCostActionArg) {
|
||||
|
||||
}
|
||||
|
||||
func (m1 *SnowWorkerM1) BeginOverCostAction(useTimeTick int64) {
|
||||
|
||||
}
|
||||
|
||||
func (m1 *SnowWorkerM1) EndOverCostAction(useTimeTick int64) {
|
||||
if m1._TermIndex > 10000 {
|
||||
m1._TermIndex = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (m1 *SnowWorkerM1) BeginTurnBackAction(useTimeTick int64) {
|
||||
|
||||
}
|
||||
|
||||
func (m1 *SnowWorkerM1) EndTurnBackAction(useTimeTick int64) {
|
||||
|
||||
}
|
||||
|
||||
func (m1 *SnowWorkerM1) NextOverCostId() int64 {
|
||||
currentTimeTick := m1.GetCurrentTimeTick()
|
||||
if currentTimeTick > m1._LastTimeTick {
|
||||
m1.EndOverCostAction(currentTimeTick)
|
||||
m1._LastTimeTick = currentTimeTick
|
||||
m1._CurrentSeqNumber = m1.MinSeqNumber
|
||||
m1._IsOverCost = false
|
||||
m1._OverCostCountInOneTerm = 0
|
||||
m1._GenCountInOneTerm = 0
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
if m1._OverCostCountInOneTerm >= m1.TopOverCostCount {
|
||||
m1.EndOverCostAction(currentTimeTick)
|
||||
m1._LastTimeTick = m1.GetNextTimeTick()
|
||||
m1._CurrentSeqNumber = m1.MinSeqNumber
|
||||
m1._IsOverCost = false
|
||||
m1._OverCostCountInOneTerm = 0
|
||||
m1._GenCountInOneTerm = 0
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
if m1._CurrentSeqNumber > m1.MaxSeqNumber {
|
||||
m1._LastTimeTick++
|
||||
m1._CurrentSeqNumber = m1.MinSeqNumber
|
||||
m1._IsOverCost = true
|
||||
m1._OverCostCountInOneTerm++
|
||||
m1._GenCountInOneTerm++
|
||||
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
|
||||
m1._GenCountInOneTerm++
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
|
||||
// NextNormalID .
|
||||
func (m1 *SnowWorkerM1) NextNormalId() int64 {
|
||||
currentTimeTick := m1.GetCurrentTimeTick()
|
||||
if currentTimeTick < m1._LastTimeTick {
|
||||
if m1._TurnBackTimeTick < 1 {
|
||||
m1._TurnBackTimeTick = m1._LastTimeTick - 1
|
||||
m1._TurnBackIndex++
|
||||
// 每毫秒序列数的前5位是预留位,0用于手工新值,1-4是时间回拨次序
|
||||
// 最多4次回拨(防止回拨重叠)
|
||||
if m1._TurnBackIndex > 4 {
|
||||
m1._TurnBackIndex = 1
|
||||
}
|
||||
m1.BeginTurnBackAction(m1._TurnBackTimeTick)
|
||||
}
|
||||
|
||||
// time.Sleep(time.Duration(1) * time.Millisecond)
|
||||
return m1.CalcTurnBackId(m1._TurnBackTimeTick)
|
||||
}
|
||||
|
||||
// 时间追平时,_TurnBackTimeTick清零
|
||||
if m1._TurnBackTimeTick > 0 {
|
||||
m1.EndTurnBackAction(m1._TurnBackTimeTick)
|
||||
m1._TurnBackTimeTick = 0
|
||||
}
|
||||
|
||||
if currentTimeTick > m1._LastTimeTick {
|
||||
m1._LastTimeTick = currentTimeTick
|
||||
m1._CurrentSeqNumber = m1.MinSeqNumber
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
|
||||
if m1._CurrentSeqNumber > m1.MaxSeqNumber {
|
||||
m1.BeginOverCostAction(currentTimeTick)
|
||||
m1._TermIndex++
|
||||
m1._LastTimeTick++
|
||||
m1._CurrentSeqNumber = m1.MinSeqNumber
|
||||
m1._IsOverCost = true
|
||||
m1._OverCostCountInOneTerm = 1
|
||||
m1._GenCountInOneTerm = 1
|
||||
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
|
||||
return m1.CalcId(m1._LastTimeTick)
|
||||
}
|
||||
|
||||
// CalcID .
|
||||
func (m1 *SnowWorkerM1) CalcId(useTimeTick int64) int64 {
|
||||
result := int64(useTimeTick<<m1._TimestampShift) + int64(m1.WorkerId<<m1.SeqBitLength) + int64(m1._CurrentSeqNumber)
|
||||
m1._CurrentSeqNumber++
|
||||
return result
|
||||
}
|
||||
|
||||
// CalcTurnBackID .
|
||||
func (m1 *SnowWorkerM1) CalcTurnBackId(useTimeTick int64) int64 {
|
||||
result := int64(useTimeTick<<m1._TimestampShift) + int64(m1.WorkerId<<m1.SeqBitLength) + int64(m1._TurnBackIndex)
|
||||
m1._TurnBackTimeTick--
|
||||
return result
|
||||
}
|
||||
|
||||
// GetCurrentTimeTick .
|
||||
func (m1 *SnowWorkerM1) GetCurrentTimeTick() int64 {
|
||||
var millis = time.Now().UnixNano() / 1e6
|
||||
return millis - m1.BaseTime
|
||||
}
|
||||
|
||||
// GetNextTimeTick .
|
||||
func (m1 *SnowWorkerM1) GetNextTimeTick() int64 {
|
||||
tempTimeTicker := m1.GetCurrentTimeTick()
|
||||
for tempTimeTicker <= m1._LastTimeTick {
|
||||
tempTimeTicker = m1.GetCurrentTimeTick()
|
||||
}
|
||||
return tempTimeTicker
|
||||
}
|
||||
|
||||
// NextId .
|
||||
func (m1 *SnowWorkerM1) NextId() int64 {
|
||||
m1.Lock()
|
||||
defer m1.Unlock()
|
||||
if m1._IsOverCost {
|
||||
return m1.NextOverCostId()
|
||||
} else {
|
||||
return m1.NextNormalId()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package uniqueid
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type SnowWorkerM2 struct {
|
||||
*SnowWorkerM1
|
||||
}
|
||||
|
||||
func NewSnowWorkerM2(options *IdGeneratorOptions) ISnowWorker {
|
||||
return &SnowWorkerM2{
|
||||
NewSnowWorkerM1(options).(*SnowWorkerM1),
|
||||
}
|
||||
}
|
||||
|
||||
func (m2 SnowWorkerM2) NextId() int64 {
|
||||
m2.Lock()
|
||||
defer m2.Unlock()
|
||||
currentTimeTick := m2.GetCurrentTimeTick()
|
||||
if m2._LastTimeTick == currentTimeTick {
|
||||
m2._CurrentSeqNumber++
|
||||
if m2._CurrentSeqNumber > m2.MaxSeqNumber {
|
||||
m2._CurrentSeqNumber = m2.MinSeqNumber
|
||||
currentTimeTick = m2.GetNextTimeTick()
|
||||
}
|
||||
} else {
|
||||
m2._CurrentSeqNumber = m2.MinSeqNumber
|
||||
}
|
||||
if currentTimeTick < m2._LastTimeTick {
|
||||
fmt.Println("Time error for {0} milliseconds", strconv.FormatInt(m2._LastTimeTick-currentTimeTick, 10))
|
||||
}
|
||||
m2._LastTimeTick = currentTimeTick
|
||||
result := int64(currentTimeTick<<m2._TimestampShift) + int64(m2.WorkerId<<m2.SeqBitLength) + int64(m2._CurrentSeqNumber)
|
||||
return result
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
{ "recommendations": ["expo.vscode-expo-tools"] }
|
||||
{ "recommendations": ["expo.vscode-expo-tools", "gluestack.gluestack-vscode"] }
|
||||
@@ -1,50 +0,0 @@
|
||||
# Welcome to your Expo app 👋
|
||||
|
||||
This is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.npmjs.com/package/create-expo-app).
|
||||
|
||||
## Get started
|
||||
|
||||
1. Install dependencies
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. Start the app
|
||||
|
||||
```bash
|
||||
npx expo start
|
||||
```
|
||||
|
||||
In the output, you'll find options to open the app in a
|
||||
|
||||
- [development build](https://docs.expo.dev/develop/development-builds/introduction/)
|
||||
- [Android emulator](https://docs.expo.dev/workflow/android-studio-emulator/)
|
||||
- [iOS simulator](https://docs.expo.dev/workflow/ios-simulator/)
|
||||
- [Expo Go](https://expo.dev/go), a limited sandbox for trying out app development with Expo
|
||||
|
||||
You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
|
||||
|
||||
## Get a fresh project
|
||||
|
||||
When you're ready, run:
|
||||
|
||||
```bash
|
||||
npm run reset-project
|
||||
```
|
||||
|
||||
This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
|
||||
|
||||
## Learn more
|
||||
|
||||
To learn more about developing your project with Expo, look at the following resources:
|
||||
|
||||
- [Expo documentation](https://docs.expo.dev/): Learn fundamentals, or go into advanced topics with our [guides](https://docs.expo.dev/guides).
|
||||
- [Learn Expo tutorial](https://docs.expo.dev/tutorial/introduction/): Follow a step-by-step tutorial where you'll create a project that runs on Android, iOS, and the web.
|
||||
|
||||
## Join the community
|
||||
|
||||
Join our community of developers creating universal apps.
|
||||
|
||||
- [Expo on GitHub](https://github.com/expo/expo): View our open source platform and contribute.
|
||||
- [Discord community](https://chat.expo.dev): Chat with Expo users and ask questions.
|
||||
@@ -23,7 +23,8 @@
|
||||
},
|
||||
"web": {
|
||||
"output": "static",
|
||||
"favicon": "./assets/images/favicon.png"
|
||||
"favicon": "./assets/images/favicon.png",
|
||||
"bundler": "metro"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { Tabs } from 'expo-router';
|
||||
import React from 'react';
|
||||
import { Tabs } from 'expo-router'
|
||||
import * as React from 'react'
|
||||
|
||||
import { HapticTab } from '@/components/haptic-tab';
|
||||
import { IconSymbol } from '@/components/ui/icon-symbol';
|
||||
import { Colors } from '@/constants/theme';
|
||||
import { useColorScheme } from '@/hooks/use-color-scheme';
|
||||
import { HapticTab } from '@/components/haptic-tab'
|
||||
import { IconSymbol } from '@/components/ui/icon-symbol'
|
||||
import { Colors } from '@/constants/theme'
|
||||
import { useColorScheme } from '@/hooks/use-color-scheme'
|
||||
|
||||
export default function TabLayout() {
|
||||
const colorScheme = useColorScheme();
|
||||
const colorScheme = useColorScheme()
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
@@ -15,7 +15,8 @@ export default function TabLayout() {
|
||||
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
|
||||
headerShown: false,
|
||||
tabBarButton: HapticTab,
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
<Tabs.Screen
|
||||
name="index"
|
||||
options={{
|
||||
@@ -31,5 +32,5 @@ export default function TabLayout() {
|
||||
}}
|
||||
/>
|
||||
</Tabs>
|
||||
);
|
||||
)
|
||||
}
|
||||
@@ -1,44 +1,53 @@
|
||||
import { Image } from 'expo-image';
|
||||
import { Platform, StyleSheet } from 'react-native';
|
||||
import { Image } from 'expo-image'
|
||||
import { Platform, StyleSheet } from 'react-native'
|
||||
|
||||
import { Collapsible } from '@/components/ui/collapsible';
|
||||
import { ExternalLink } from '@/components/external-link';
|
||||
import ParallaxScrollView from '@/components/parallax-scroll-view';
|
||||
import { ThemedText } from '@/components/themed-text';
|
||||
import { ThemedView } from '@/components/themed-view';
|
||||
import { IconSymbol } from '@/components/ui/icon-symbol';
|
||||
import { Fonts } from '@/constants/theme';
|
||||
import { ExternalLink } from '@/components/external-link'
|
||||
import ParallaxScrollView from '@/components/parallax-scroll-view'
|
||||
import { ThemedText } from '@/components/themed-text'
|
||||
import { ThemedView } from '@/components/themed-view'
|
||||
import { Collapsible } from '@/components/ui/collapsible'
|
||||
import { IconSymbol } from '@/components/ui/icon-symbol'
|
||||
import { Fonts } from '@/constants/theme'
|
||||
|
||||
export default function TabTwoScreen() {
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#D0D0D0', dark: '#353636' }}
|
||||
headerImage={
|
||||
headerImage={(
|
||||
<IconSymbol
|
||||
size={310}
|
||||
color="#808080"
|
||||
name="chevron.left.forwardslash.chevron.right"
|
||||
style={styles.headerImage}
|
||||
/>
|
||||
}>
|
||||
)}
|
||||
>
|
||||
<ThemedView style={styles.titleContainer}>
|
||||
<ThemedText
|
||||
type="title"
|
||||
style={{
|
||||
fontFamily: Fonts.rounded,
|
||||
}}>
|
||||
}}
|
||||
>
|
||||
Explore
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedText>This app includes example code to help you get started.</ThemedText>
|
||||
<Collapsible title="File-based routing">
|
||||
<ThemedText>
|
||||
This app has two screens:{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> and{' '}
|
||||
This app has two screens:
|
||||
{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText>
|
||||
{' '}
|
||||
and
|
||||
{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/explore.tsx</ThemedText>
|
||||
</ThemedText>
|
||||
<ThemedText>
|
||||
The layout file in <ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>{' '}
|
||||
The layout file in
|
||||
{' '}
|
||||
<ThemedText type="defaultSemiBold">app/(tabs)/_layout.tsx</ThemedText>
|
||||
{' '}
|
||||
sets up the tab navigator.
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/router/introduction">
|
||||
@@ -47,14 +56,24 @@ export default function TabTwoScreen() {
|
||||
</Collapsible>
|
||||
<Collapsible title="Android, iOS, and web support">
|
||||
<ThemedText>
|
||||
You can open this project on Android, iOS, and the web. To open the web version, press{' '}
|
||||
<ThemedText type="defaultSemiBold">w</ThemedText> in the terminal running this project.
|
||||
You can open this project on Android, iOS, and the web. To open the web version, press
|
||||
{' '}
|
||||
<ThemedText type="defaultSemiBold">w</ThemedText>
|
||||
{' '}
|
||||
in the terminal running this project.
|
||||
</ThemedText>
|
||||
</Collapsible>
|
||||
<Collapsible title="Images">
|
||||
<ThemedText>
|
||||
For static images, you can use the <ThemedText type="defaultSemiBold">@2x</ThemedText> and{' '}
|
||||
<ThemedText type="defaultSemiBold">@3x</ThemedText> suffixes to provide files for
|
||||
For static images, you can use the
|
||||
{' '}
|
||||
<ThemedText type="defaultSemiBold">@2x</ThemedText>
|
||||
{' '}
|
||||
and
|
||||
{' '}
|
||||
<ThemedText type="defaultSemiBold">@3x</ThemedText>
|
||||
{' '}
|
||||
suffixes to provide files for
|
||||
different screen densities
|
||||
</ThemedText>
|
||||
<Image
|
||||
@@ -67,8 +86,11 @@ export default function TabTwoScreen() {
|
||||
</Collapsible>
|
||||
<Collapsible title="Light and dark mode components">
|
||||
<ThemedText>
|
||||
This template has light and dark mode support. The{' '}
|
||||
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText> hook lets you inspect
|
||||
This template has light and dark mode support. The
|
||||
{' '}
|
||||
<ThemedText type="defaultSemiBold">useColorScheme()</ThemedText>
|
||||
{' '}
|
||||
hook lets you inspect
|
||||
what the user's current color scheme is, and so you can adjust UI colors accordingly.
|
||||
</ThemedText>
|
||||
<ExternalLink href="https://docs.expo.dev/develop/user-interface/color-themes/">
|
||||
@@ -77,25 +99,33 @@ export default function TabTwoScreen() {
|
||||
</Collapsible>
|
||||
<Collapsible title="Animations">
|
||||
<ThemedText>
|
||||
This template includes an example of an animated component. The{' '}
|
||||
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText> component uses
|
||||
the powerful{' '}
|
||||
This template includes an example of an animated component. The
|
||||
{' '}
|
||||
<ThemedText type="defaultSemiBold">components/HelloWave.tsx</ThemedText>
|
||||
{' '}
|
||||
component uses
|
||||
the powerful
|
||||
{' '}
|
||||
<ThemedText type="defaultSemiBold" style={{ fontFamily: Fonts.mono }}>
|
||||
react-native-reanimated
|
||||
</ThemedText>{' '}
|
||||
</ThemedText>
|
||||
{' '}
|
||||
library to create a waving hand animation.
|
||||
</ThemedText>
|
||||
{Platform.select({
|
||||
ios: (
|
||||
<ThemedText>
|
||||
The <ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>{' '}
|
||||
The
|
||||
{' '}
|
||||
<ThemedText type="defaultSemiBold">components/ParallaxScrollView.tsx</ThemedText>
|
||||
{' '}
|
||||
component provides a parallax effect for the header image.
|
||||
</ThemedText>
|
||||
),
|
||||
})}
|
||||
</Collapsible>
|
||||
</ParallaxScrollView>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@@ -109,4 +139,4 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
gap: 8,
|
||||
},
|
||||
});
|
||||
})
|
||||
@@ -1,98 +1,12 @@
|
||||
import { Image } from 'expo-image';
|
||||
import { Platform, StyleSheet } from 'react-native';
|
||||
|
||||
import { HelloWave } from '@/components/hello-wave';
|
||||
import ParallaxScrollView from '@/components/parallax-scroll-view';
|
||||
import { ThemedText } from '@/components/themed-text';
|
||||
import { ThemedView } from '@/components/themed-view';
|
||||
import { Link } from 'expo-router';
|
||||
import { Platform, StyleSheet, Text, View } from 'react-native'
|
||||
import { Button, ButtonIcon, ButtonSpinner, ButtonText } from '@/components/ui/button'
|
||||
|
||||
export default function HomeScreen() {
|
||||
return (
|
||||
<ParallaxScrollView
|
||||
headerBackgroundColor={{ light: '#A1CEDC', dark: '#1D3D47' }}
|
||||
headerImage={
|
||||
<Image
|
||||
source={require('@/assets/images/partial-react-logo.png')}
|
||||
style={styles.reactLogo}
|
||||
/>
|
||||
}>
|
||||
<ThemedView style={styles.titleContainer}>
|
||||
<ThemedText type="title">Welcome!</ThemedText>
|
||||
<HelloWave />
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.stepContainer}>
|
||||
<ThemedText type="subtitle">Step 1: Try it</ThemedText>
|
||||
<ThemedText>
|
||||
Edit <ThemedText type="defaultSemiBold">app/(tabs)/index.tsx</ThemedText> to see changes.
|
||||
Press{' '}
|
||||
<ThemedText type="defaultSemiBold">
|
||||
{Platform.select({
|
||||
ios: 'cmd + d',
|
||||
android: 'cmd + m',
|
||||
web: 'F12',
|
||||
})}
|
||||
</ThemedText>{' '}
|
||||
to open developer tools.
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.stepContainer}>
|
||||
<Link href="/modal">
|
||||
<Link.Trigger>
|
||||
<ThemedText type="subtitle">Step 2: Explore</ThemedText>
|
||||
</Link.Trigger>
|
||||
<Link.Preview />
|
||||
<Link.Menu>
|
||||
<Link.MenuAction title="Action" icon="cube" onPress={() => alert('Action pressed')} />
|
||||
<Link.MenuAction
|
||||
title="Share"
|
||||
icon="square.and.arrow.up"
|
||||
onPress={() => alert('Share pressed')}
|
||||
/>
|
||||
<Link.Menu title="More" icon="ellipsis">
|
||||
<Link.MenuAction
|
||||
title="Delete"
|
||||
icon="trash"
|
||||
destructive
|
||||
onPress={() => alert('Delete pressed')}
|
||||
/>
|
||||
</Link.Menu>
|
||||
</Link.Menu>
|
||||
</Link>
|
||||
|
||||
<ThemedText>
|
||||
{`Tap the Explore tab to learn more about what's included in this starter app.`}
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
<ThemedView style={styles.stepContainer}>
|
||||
<ThemedText type="subtitle">Step 3: Get a fresh start</ThemedText>
|
||||
<ThemedText>
|
||||
{`When you're ready, run `}
|
||||
<ThemedText type="defaultSemiBold">npm run reset-project</ThemedText> to get a fresh{' '}
|
||||
<ThemedText type="defaultSemiBold">app</ThemedText> directory. This will move the current{' '}
|
||||
<ThemedText type="defaultSemiBold">app</ThemedText> to{' '}
|
||||
<ThemedText type="defaultSemiBold">app-example</ThemedText>.
|
||||
</ThemedText>
|
||||
</ThemedView>
|
||||
</ParallaxScrollView>
|
||||
);
|
||||
<View className="flex-1 items-center justify-center dark:bg-black">
|
||||
<Button action="primary" size="sm" isDisabled={false}>
|
||||
<ButtonText>Hello World</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
titleContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
stepContainer: {
|
||||
gap: 8,
|
||||
marginBottom: 8,
|
||||
},
|
||||
reactLogo: {
|
||||
height: 178,
|
||||
width: 290,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
position: 'absolute',
|
||||
},
|
||||
});
|
||||
@@ -1,24 +1,38 @@
|
||||
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
||||
import { Stack } from 'expo-router';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import 'react-native-reanimated';
|
||||
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'
|
||||
import { Stack } from 'expo-router'
|
||||
import { StatusBar } from 'expo-status-bar'
|
||||
import { useEffect } from 'react'
|
||||
import { Appearance } from 'react-native'
|
||||
import { GluestackUIProvider } from '@/components/ui/gluestack-ui-provider'
|
||||
import { useAppStore } from '@/store/app'
|
||||
import 'react-native-reanimated'
|
||||
|
||||
import { useColorScheme } from '@/hooks/use-color-scheme';
|
||||
import '@/styles/global.css'
|
||||
|
||||
export const unstable_settings = {
|
||||
anchor: '(tabs)',
|
||||
};
|
||||
}
|
||||
|
||||
export default function RootLayout() {
|
||||
const colorScheme = useColorScheme();
|
||||
const appStore = useAppStore()
|
||||
|
||||
useEffect(() => {
|
||||
const sub = Appearance.addChangeListener(() => {
|
||||
useAppStore.getState().syncWithSystem()
|
||||
})
|
||||
|
||||
return () => sub.remove()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||
<Stack>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
|
||||
</Stack>
|
||||
<StatusBar style="auto" />
|
||||
</ThemeProvider>
|
||||
);
|
||||
<GluestackUIProvider mode={appStore.isDark ? 'dark' : 'light'}>
|
||||
<ThemeProvider value={appStore.isDark ? DarkTheme : DefaultTheme}>
|
||||
<Stack>
|
||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||
<Stack.Screen name="modal" options={{ presentation: 'modal', title: 'Modal' }} />
|
||||
</Stack>
|
||||
<StatusBar style="auto" />
|
||||
</ThemeProvider>
|
||||
</GluestackUIProvider>
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
api.cache(true)
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
};
|
||||
};
|
||||
presets: [
|
||||
['babel-preset-expo', { jsxImportSource: 'nativewind' }],
|
||||
'nativewind/babel',
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
'use client'
|
||||
import type { VariantProps } from '@gluestack-ui/utils/nativewind-utils'
|
||||
import { createButton } from '@gluestack-ui/core/button/creator'
|
||||
import { PrimitiveIcon, UIIcon } from '@gluestack-ui/core/icon/creator'
|
||||
import {
|
||||
tva,
|
||||
useStyleContext,
|
||||
|
||||
withStyleContext,
|
||||
} from '@gluestack-ui/utils/nativewind-utils'
|
||||
import { cssInterop } from 'nativewind'
|
||||
import * as React from 'react'
|
||||
import { ActivityIndicator, Pressable, Text, View } from 'react-native'
|
||||
|
||||
const SCOPE = 'BUTTON'
|
||||
|
||||
const Root = withStyleContext(Pressable, SCOPE)
|
||||
|
||||
const UIButton = createButton({
|
||||
Root,
|
||||
Text,
|
||||
Group: View,
|
||||
Spinner: ActivityIndicator,
|
||||
Icon: UIIcon,
|
||||
})
|
||||
|
||||
cssInterop(PrimitiveIcon, {
|
||||
className: {
|
||||
target: 'style',
|
||||
nativeStyleToProp: {
|
||||
height: true,
|
||||
width: true,
|
||||
fill: true,
|
||||
color: 'classNameColor',
|
||||
stroke: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const buttonStyle = tva({
|
||||
base: 'group/button rounded bg-primary-500 flex-row items-center justify-center data-[focus-visible=true]:web:outline-none data-[focus-visible=true]:web:ring-2 data-[disabled=true]:opacity-40 gap-2',
|
||||
variants: {
|
||||
action: {
|
||||
primary:
|
||||
'bg-primary-500 data-[hover=true]:bg-primary-600 data-[active=true]:bg-primary-700 border-primary-300 data-[hover=true]:border-primary-400 data-[active=true]:border-primary-500 data-[focus-visible=true]:web:ring-indicator-info',
|
||||
secondary:
|
||||
'bg-secondary-500 border-secondary-300 data-[hover=true]:bg-secondary-600 data-[hover=true]:border-secondary-400 data-[active=true]:bg-secondary-700 data-[active=true]:border-secondary-700 data-[focus-visible=true]:web:ring-indicator-info',
|
||||
positive:
|
||||
'bg-success-500 border-success-300 data-[hover=true]:bg-success-600 data-[hover=true]:border-success-400 data-[active=true]:bg-success-700 data-[active=true]:border-success-500 data-[focus-visible=true]:web:ring-indicator-info',
|
||||
negative:
|
||||
'bg-error-500 border-error-300 data-[hover=true]:bg-error-600 data-[hover=true]:border-error-400 data-[active=true]:bg-error-700 data-[active=true]:border-error-500 data-[focus-visible=true]:web:ring-indicator-info',
|
||||
default:
|
||||
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
|
||||
},
|
||||
variant: {
|
||||
link: 'px-0',
|
||||
outline:
|
||||
'bg-transparent border data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
|
||||
solid: '',
|
||||
},
|
||||
|
||||
size: {
|
||||
xs: 'px-3.5 h-8',
|
||||
sm: 'px-4 h-9',
|
||||
md: 'px-5 h-10',
|
||||
lg: 'px-6 h-11',
|
||||
xl: 'px-7 h-12',
|
||||
},
|
||||
},
|
||||
compoundVariants: [
|
||||
{
|
||||
action: 'primary',
|
||||
variant: 'link',
|
||||
class:
|
||||
'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'secondary',
|
||||
variant: 'link',
|
||||
class:
|
||||
'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'positive',
|
||||
variant: 'link',
|
||||
class:
|
||||
'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'negative',
|
||||
variant: 'link',
|
||||
class:
|
||||
'px-0 bg-transparent data-[hover=true]:bg-transparent data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'primary',
|
||||
variant: 'outline',
|
||||
class:
|
||||
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'secondary',
|
||||
variant: 'outline',
|
||||
class:
|
||||
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'positive',
|
||||
variant: 'outline',
|
||||
class:
|
||||
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
|
||||
},
|
||||
{
|
||||
action: 'negative',
|
||||
variant: 'outline',
|
||||
class:
|
||||
'bg-transparent data-[hover=true]:bg-background-50 data-[active=true]:bg-transparent',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const buttonTextStyle = tva({
|
||||
base: 'text-typography-0 font-semibold web:select-none',
|
||||
parentVariants: {
|
||||
action: {
|
||||
primary:
|
||||
'text-primary-600 data-[hover=true]:text-primary-600 data-[active=true]:text-primary-700',
|
||||
secondary:
|
||||
'text-typography-500 data-[hover=true]:text-typography-600 data-[active=true]:text-typography-700',
|
||||
positive:
|
||||
'text-success-600 data-[hover=true]:text-success-600 data-[active=true]:text-success-700',
|
||||
negative:
|
||||
'text-error-600 data-[hover=true]:text-error-600 data-[active=true]:text-error-700',
|
||||
},
|
||||
variant: {
|
||||
link: 'data-[hover=true]:underline data-[active=true]:underline',
|
||||
outline: '',
|
||||
solid:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
size: {
|
||||
xs: 'text-xs',
|
||||
sm: 'text-sm',
|
||||
md: 'text-base',
|
||||
lg: 'text-lg',
|
||||
xl: 'text-xl',
|
||||
},
|
||||
},
|
||||
parentCompoundVariants: [
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'primary',
|
||||
class:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'secondary',
|
||||
class:
|
||||
'text-typography-800 data-[hover=true]:text-typography-800 data-[active=true]:text-typography-800',
|
||||
},
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'positive',
|
||||
class:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'negative',
|
||||
class:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'primary',
|
||||
class:
|
||||
'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'secondary',
|
||||
class:
|
||||
'text-typography-500 data-[hover=true]:text-primary-600 data-[active=true]:text-typography-700',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'positive',
|
||||
class:
|
||||
'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
|
||||
},
|
||||
{
|
||||
variant: 'outline',
|
||||
action: 'negative',
|
||||
class:
|
||||
'text-primary-500 data-[hover=true]:text-primary-500 data-[active=true]:text-primary-500',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const buttonIconStyle = tva({
|
||||
base: 'fill-none',
|
||||
parentVariants: {
|
||||
variant: {
|
||||
link: 'data-[hover=true]:underline data-[active=true]:underline',
|
||||
outline: '',
|
||||
solid:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
size: {
|
||||
xs: 'h-3.5 w-3.5',
|
||||
sm: 'h-4 w-4',
|
||||
md: 'h-[18px] w-[18px]',
|
||||
lg: 'h-[18px] w-[18px]',
|
||||
xl: 'h-5 w-5',
|
||||
},
|
||||
action: {
|
||||
primary:
|
||||
'text-primary-600 data-[hover=true]:text-primary-600 data-[active=true]:text-primary-700',
|
||||
secondary:
|
||||
'text-typography-500 data-[hover=true]:text-typography-600 data-[active=true]:text-typography-700',
|
||||
positive:
|
||||
'text-success-600 data-[hover=true]:text-success-600 data-[active=true]:text-success-700',
|
||||
|
||||
negative:
|
||||
'text-error-600 data-[hover=true]:text-error-600 data-[active=true]:text-error-700',
|
||||
},
|
||||
},
|
||||
parentCompoundVariants: [
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'primary',
|
||||
class:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'secondary',
|
||||
class:
|
||||
'text-typography-800 data-[hover=true]:text-typography-800 data-[active=true]:text-typography-800',
|
||||
},
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'positive',
|
||||
class:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
{
|
||||
variant: 'solid',
|
||||
action: 'negative',
|
||||
class:
|
||||
'text-typography-0 data-[hover=true]:text-typography-0 data-[active=true]:text-typography-0',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const buttonGroupStyle = tva({
|
||||
base: '',
|
||||
variants: {
|
||||
space: {
|
||||
'xs': 'gap-1',
|
||||
'sm': 'gap-2',
|
||||
'md': 'gap-3',
|
||||
'lg': 'gap-4',
|
||||
'xl': 'gap-5',
|
||||
'2xl': 'gap-6',
|
||||
'3xl': 'gap-7',
|
||||
'4xl': 'gap-8',
|
||||
},
|
||||
isAttached: {
|
||||
true: 'gap-0',
|
||||
},
|
||||
flexDirection: {
|
||||
'row': 'flex-row',
|
||||
'column': 'flex-col',
|
||||
'row-reverse': 'flex-row-reverse',
|
||||
'column-reverse': 'flex-col-reverse',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
type IButtonProps = Omit<
|
||||
React.ComponentPropsWithoutRef<typeof UIButton>,
|
||||
'context'
|
||||
>
|
||||
& VariantProps<typeof buttonStyle> & { className?: string }
|
||||
|
||||
const Button = React.forwardRef<
|
||||
React.ElementRef<typeof UIButton>,
|
||||
IButtonProps
|
||||
>(
|
||||
(
|
||||
{ className, variant = 'solid', size = 'md', action = 'primary', ...props },
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<UIButton
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={buttonStyle({ variant, size, action, class: className })}
|
||||
context={{ variant, size, action }}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
type IButtonTextProps = React.ComponentPropsWithoutRef<typeof UIButton.Text>
|
||||
& VariantProps<typeof buttonTextStyle> & { className?: string }
|
||||
|
||||
const ButtonText = React.forwardRef<
|
||||
React.ElementRef<typeof UIButton.Text>,
|
||||
IButtonTextProps
|
||||
>(({ className, variant, size, action, ...props }, ref) => {
|
||||
const {
|
||||
variant: parentVariant,
|
||||
size: parentSize,
|
||||
action: parentAction,
|
||||
} = useStyleContext(SCOPE)
|
||||
|
||||
return (
|
||||
<UIButton.Text
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={buttonTextStyle({
|
||||
parentVariants: {
|
||||
variant: parentVariant,
|
||||
size: parentSize,
|
||||
action: parentAction,
|
||||
},
|
||||
variant,
|
||||
size,
|
||||
action,
|
||||
class: className,
|
||||
})}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
const ButtonSpinner = UIButton.Spinner
|
||||
|
||||
type IButtonIcon = React.ComponentPropsWithoutRef<typeof UIButton.Icon>
|
||||
& VariantProps<typeof buttonIconStyle> & {
|
||||
className?: string | undefined
|
||||
as?: React.ElementType
|
||||
height?: number
|
||||
width?: number
|
||||
}
|
||||
|
||||
const ButtonIcon = React.forwardRef<
|
||||
React.ElementRef<typeof UIButton.Icon>,
|
||||
IButtonIcon
|
||||
>(({ className, size, ...props }, ref) => {
|
||||
const {
|
||||
variant: parentVariant,
|
||||
size: parentSize,
|
||||
action: parentAction,
|
||||
} = useStyleContext(SCOPE)
|
||||
|
||||
if (typeof size === 'number') {
|
||||
return (
|
||||
<UIButton.Icon
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={buttonIconStyle({ class: className })}
|
||||
size={size}
|
||||
/>
|
||||
)
|
||||
}
|
||||
else if (
|
||||
(props.height !== undefined || props.width !== undefined)
|
||||
&& size === undefined
|
||||
) {
|
||||
return (
|
||||
<UIButton.Icon
|
||||
ref={ref}
|
||||
{...props}
|
||||
className={buttonIconStyle({ class: className })}
|
||||
/>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<UIButton.Icon
|
||||
{...props}
|
||||
className={buttonIconStyle({
|
||||
parentVariants: {
|
||||
size: parentSize,
|
||||
variant: parentVariant,
|
||||
action: parentAction,
|
||||
},
|
||||
size,
|
||||
class: className,
|
||||
})}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
type IButtonGroupProps = React.ComponentPropsWithoutRef<typeof UIButton.Group>
|
||||
& VariantProps<typeof buttonGroupStyle>
|
||||
|
||||
const ButtonGroup = React.forwardRef<
|
||||
React.ElementRef<typeof UIButton.Group>,
|
||||
IButtonGroupProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
className,
|
||||
space = 'md',
|
||||
isAttached = false,
|
||||
flexDirection = 'column',
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
return (
|
||||
<UIButton.Group
|
||||
className={buttonGroupStyle({
|
||||
class: className,
|
||||
space,
|
||||
isAttached,
|
||||
flexDirection,
|
||||
})}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Button.displayName = 'Button'
|
||||
ButtonText.displayName = 'ButtonText'
|
||||
ButtonSpinner.displayName = 'ButtonSpinner'
|
||||
ButtonIcon.displayName = 'ButtonIcon'
|
||||
ButtonGroup.displayName = 'ButtonGroup'
|
||||
|
||||
export { Button, ButtonGroup, ButtonIcon, ButtonSpinner, ButtonText }
|
||||
@@ -0,0 +1,309 @@
|
||||
'use client'
|
||||
import { vars } from 'nativewind'
|
||||
|
||||
export const config = {
|
||||
light: vars({
|
||||
'--color-primary-0': '240 249 255',
|
||||
'--color-primary-50': '224 242 254',
|
||||
'--color-primary-100': '186 230 253',
|
||||
'--color-primary-200': '125 211 252',
|
||||
'--color-primary-300': '56 189 248',
|
||||
'--color-primary-400': '14 165 233',
|
||||
'--color-primary-500': '2 132 199',
|
||||
'--color-primary-600': '3 105 161',
|
||||
'--color-primary-700': '7 89 133',
|
||||
'--color-primary-800': '12 74 110',
|
||||
'--color-primary-900': '8 47 73',
|
||||
'--color-primary-950': '2 22 36',
|
||||
|
||||
/* Secondary */
|
||||
'--color-secondary-0': '253 253 253',
|
||||
'--color-secondary-50': '251 251 251',
|
||||
'--color-secondary-100': '246 246 246',
|
||||
'--color-secondary-200': '242 242 242',
|
||||
'--color-secondary-300': '237 237 237',
|
||||
'--color-secondary-400': '230 230 231',
|
||||
'--color-secondary-500': '217 217 219',
|
||||
'--color-secondary-600': '198 199 199',
|
||||
'--color-secondary-700': '189 189 189',
|
||||
'--color-secondary-800': '177 177 177',
|
||||
'--color-secondary-900': '165 164 164',
|
||||
'--color-secondary-950': '157 157 157',
|
||||
|
||||
/* Tertiary */
|
||||
'--color-tertiary-0': '255 250 245',
|
||||
'--color-tertiary-50': '255 242 229',
|
||||
'--color-tertiary-100': '255 233 213',
|
||||
'--color-tertiary-200': '254 209 170',
|
||||
'--color-tertiary-300': '253 180 116',
|
||||
'--color-tertiary-400': '251 157 75',
|
||||
'--color-tertiary-500': '231 129 40',
|
||||
'--color-tertiary-600': '215 117 31',
|
||||
'--color-tertiary-700': '180 98 26',
|
||||
'--color-tertiary-800': '130 73 23',
|
||||
'--color-tertiary-900': '108 61 19',
|
||||
'--color-tertiary-950': '84 49 18',
|
||||
|
||||
/* Error */
|
||||
'--color-error-0': '254 233 233',
|
||||
'--color-error-50': '254 226 226',
|
||||
'--color-error-100': '254 202 202',
|
||||
'--color-error-200': '252 165 165',
|
||||
'--color-error-300': '248 113 113',
|
||||
'--color-error-400': '239 68 68',
|
||||
'--color-error-500': '230 53 53',
|
||||
'--color-error-600': '220 38 38',
|
||||
'--color-error-700': '185 28 28',
|
||||
'--color-error-800': '153 27 27',
|
||||
'--color-error-900': '127 29 29',
|
||||
'--color-error-950': '83 19 19',
|
||||
|
||||
/* Success */
|
||||
'--color-success-0': '228 255 244',
|
||||
'--color-success-50': '202 255 232',
|
||||
'--color-success-100': '162 241 192',
|
||||
'--color-success-200': '132 211 162',
|
||||
'--color-success-300': '102 181 132',
|
||||
'--color-success-400': '72 151 102',
|
||||
'--color-success-500': '52 131 82',
|
||||
'--color-success-600': '42 121 72',
|
||||
'--color-success-700': '32 111 62',
|
||||
'--color-success-800': '22 101 52',
|
||||
'--color-success-900': '20 83 45',
|
||||
'--color-success-950': '27 50 36',
|
||||
|
||||
/* Warning */
|
||||
'--color-warning-0': '255 249 245',
|
||||
'--color-warning-50': '255 244 236',
|
||||
'--color-warning-100': '255 231 213',
|
||||
'--color-warning-200': '254 205 170',
|
||||
'--color-warning-300': '253 173 116',
|
||||
'--color-warning-400': '251 149 75',
|
||||
'--color-warning-500': '231 120 40',
|
||||
'--color-warning-600': '215 108 31',
|
||||
'--color-warning-700': '180 90 26',
|
||||
'--color-warning-800': '130 68 23',
|
||||
'--color-warning-900': '108 56 19',
|
||||
'--color-warning-950': '84 45 18',
|
||||
|
||||
/* Info */
|
||||
'--color-info-0': '236 248 254',
|
||||
'--color-info-50': '199 235 252',
|
||||
'--color-info-100': '162 221 250',
|
||||
'--color-info-200': '124 207 248',
|
||||
'--color-info-300': '87 194 246',
|
||||
'--color-info-400': '50 180 244',
|
||||
'--color-info-500': '13 166 242',
|
||||
'--color-info-600': '11 141 205',
|
||||
'--color-info-700': '9 115 168',
|
||||
'--color-info-800': '7 90 131',
|
||||
'--color-info-900': '5 64 93',
|
||||
'--color-info-950': '3 38 56',
|
||||
|
||||
/* Typography */
|
||||
'--color-typography-0': '254 254 255',
|
||||
'--color-typography-50': '245 245 245',
|
||||
'--color-typography-100': '229 229 229',
|
||||
'--color-typography-200': '219 219 220',
|
||||
'--color-typography-300': '212 212 212',
|
||||
'--color-typography-400': '163 163 163',
|
||||
'--color-typography-500': '140 140 140',
|
||||
'--color-typography-600': '115 115 115',
|
||||
'--color-typography-700': '82 82 82',
|
||||
'--color-typography-800': '64 64 64',
|
||||
'--color-typography-900': '38 38 39',
|
||||
'--color-typography-950': '23 23 23',
|
||||
|
||||
/* Outline */
|
||||
'--color-outline-0': '253 254 254',
|
||||
'--color-outline-50': '243 243 243',
|
||||
'--color-outline-100': '230 230 230',
|
||||
'--color-outline-200': '221 220 219',
|
||||
'--color-outline-300': '211 211 211',
|
||||
'--color-outline-400': '165 163 163',
|
||||
'--color-outline-500': '140 141 141',
|
||||
'--color-outline-600': '115 116 116',
|
||||
'--color-outline-700': '83 82 82',
|
||||
'--color-outline-800': '65 65 65',
|
||||
'--color-outline-900': '39 38 36',
|
||||
'--color-outline-950': '26 23 23',
|
||||
|
||||
/* Background */
|
||||
'--color-background-0': '255 255 255',
|
||||
'--color-background-50': '246 246 246',
|
||||
'--color-background-100': '242 241 241',
|
||||
'--color-background-200': '220 219 219',
|
||||
'--color-background-300': '213 212 212',
|
||||
'--color-background-400': '162 163 163',
|
||||
'--color-background-500': '142 142 142',
|
||||
'--color-background-600': '116 116 116',
|
||||
'--color-background-700': '83 82 82',
|
||||
'--color-background-800': '65 64 64',
|
||||
'--color-background-900': '39 38 37',
|
||||
'--color-background-950': '18 18 18',
|
||||
|
||||
/* Background Special */
|
||||
'--color-background-error': '254 241 241',
|
||||
'--color-background-warning': '255 243 234',
|
||||
'--color-background-success': '237 252 242',
|
||||
'--color-background-muted': '247 248 247',
|
||||
'--color-background-info': '235 248 254',
|
||||
|
||||
/* Focus Ring Indicator */
|
||||
'--color-indicator-primary': '55 55 55',
|
||||
'--color-indicator-info': '83 153 236',
|
||||
'--color-indicator-error': '185 28 28',
|
||||
}),
|
||||
dark: vars({
|
||||
'--color-primary-0': '2 22 36',
|
||||
'--color-primary-50': '8 47 73',
|
||||
'--color-primary-100': '12 74 110',
|
||||
'--color-primary-200': '7 89 133',
|
||||
'--color-primary-300': '3 105 161',
|
||||
'--color-primary-400': '2 132 199',
|
||||
'--color-primary-500': '56 189 248',
|
||||
'--color-primary-600': '125 211 252',
|
||||
'--color-primary-700': '186 230 253',
|
||||
'--color-primary-800': '224 242 254',
|
||||
'--color-primary-900': '240 249 255',
|
||||
'--color-primary-950': '248 252 255',
|
||||
|
||||
/* Secondary */
|
||||
'--color-secondary-0': '20 20 20',
|
||||
'--color-secondary-50': '23 23 23',
|
||||
'--color-secondary-100': '31 31 31',
|
||||
'--color-secondary-200': '39 39 39',
|
||||
'--color-secondary-300': '44 44 44',
|
||||
'--color-secondary-400': '56 57 57',
|
||||
'--color-secondary-500': '63 64 64',
|
||||
'--color-secondary-600': '86 86 86',
|
||||
'--color-secondary-700': '110 110 110',
|
||||
'--color-secondary-800': '135 135 135',
|
||||
'--color-secondary-900': '150 150 150',
|
||||
'--color-secondary-950': '164 164 164',
|
||||
|
||||
/* Tertiary */
|
||||
'--color-tertiary-0': '84 49 18',
|
||||
'--color-tertiary-50': '108 61 19',
|
||||
'--color-tertiary-100': '130 73 23',
|
||||
'--color-tertiary-200': '180 98 26',
|
||||
'--color-tertiary-300': '215 117 31',
|
||||
'--color-tertiary-400': '231 129 40',
|
||||
'--color-tertiary-500': '251 157 75',
|
||||
'--color-tertiary-600': '253 180 116',
|
||||
'--color-tertiary-700': '254 209 170',
|
||||
'--color-tertiary-800': '255 233 213',
|
||||
'--color-tertiary-900': '255 242 229',
|
||||
'--color-tertiary-950': '255 250 245',
|
||||
|
||||
/* Error */
|
||||
'--color-error-0': '83 19 19',
|
||||
'--color-error-50': '127 29 29',
|
||||
'--color-error-100': '153 27 27',
|
||||
'--color-error-200': '185 28 28',
|
||||
'--color-error-300': '220 38 38',
|
||||
'--color-error-400': '230 53 53',
|
||||
'--color-error-500': '239 68 68',
|
||||
'--color-error-600': '249 97 96',
|
||||
'--color-error-700': '229 91 90',
|
||||
'--color-error-800': '254 202 202',
|
||||
'--color-error-900': '254 226 226',
|
||||
'--color-error-950': '254 233 233',
|
||||
|
||||
/* Success */
|
||||
'--color-success-0': '27 50 36',
|
||||
'--color-success-50': '20 83 45',
|
||||
'--color-success-100': '22 101 52',
|
||||
'--color-success-200': '32 111 62',
|
||||
'--color-success-300': '42 121 72',
|
||||
'--color-success-400': '52 131 82',
|
||||
'--color-success-500': '72 151 102',
|
||||
'--color-success-600': '102 181 132',
|
||||
'--color-success-700': '132 211 162',
|
||||
'--color-success-800': '162 241 192',
|
||||
'--color-success-900': '202 255 232',
|
||||
'--color-success-950': '228 255 244',
|
||||
|
||||
/* Warning */
|
||||
'--color-warning-0': '84 45 18',
|
||||
'--color-warning-50': '108 56 19',
|
||||
'--color-warning-100': '130 68 23',
|
||||
'--color-warning-200': '180 90 26',
|
||||
'--color-warning-300': '215 108 31',
|
||||
'--color-warning-400': '231 120 40',
|
||||
'--color-warning-500': '251 149 75',
|
||||
'--color-warning-600': '253 173 116',
|
||||
'--color-warning-700': '254 205 170',
|
||||
'--color-warning-800': '255 231 213',
|
||||
'--color-warning-900': '255 244 237',
|
||||
'--color-warning-950': '255 249 245',
|
||||
|
||||
/* Info */
|
||||
'--color-info-0': '3 38 56',
|
||||
'--color-info-50': '5 64 93',
|
||||
'--color-info-100': '7 90 131',
|
||||
'--color-info-200': '9 115 168',
|
||||
'--color-info-300': '11 141 205',
|
||||
'--color-info-400': '13 166 242',
|
||||
'--color-info-500': '50 180 244',
|
||||
'--color-info-600': '87 194 246',
|
||||
'--color-info-700': '124 207 248',
|
||||
'--color-info-800': '162 221 250',
|
||||
'--color-info-900': '199 235 252',
|
||||
'--color-info-950': '236 248 254',
|
||||
|
||||
/* Typography */
|
||||
'--color-typography-0': '23 23 23',
|
||||
'--color-typography-50': '38 38 39',
|
||||
'--color-typography-100': '64 64 64',
|
||||
'--color-typography-200': '82 82 82',
|
||||
'--color-typography-300': '115 115 115',
|
||||
'--color-typography-400': '140 140 140',
|
||||
'--color-typography-500': '163 163 163',
|
||||
'--color-typography-600': '212 212 212',
|
||||
'--color-typography-700': '219 219 220',
|
||||
'--color-typography-800': '229 229 229',
|
||||
'--color-typography-900': '245 245 245',
|
||||
'--color-typography-950': '254 254 255',
|
||||
|
||||
/* Outline */
|
||||
'--color-outline-0': '26 23 23',
|
||||
'--color-outline-50': '39 38 36',
|
||||
'--color-outline-100': '65 65 65',
|
||||
'--color-outline-200': '83 82 82',
|
||||
'--color-outline-300': '115 116 116',
|
||||
'--color-outline-400': '140 141 141',
|
||||
'--color-outline-500': '165 163 163',
|
||||
'--color-outline-600': '211 211 211',
|
||||
'--color-outline-700': '221 220 219',
|
||||
'--color-outline-800': '230 230 230',
|
||||
'--color-outline-900': '243 243 243',
|
||||
'--color-outline-950': '253 254 254',
|
||||
|
||||
/* Background */
|
||||
'--color-background-0': '18 18 18',
|
||||
'--color-background-50': '39 38 37',
|
||||
'--color-background-100': '65 64 64',
|
||||
'--color-background-200': '83 82 82',
|
||||
'--color-background-300': '116 116 116',
|
||||
'--color-background-400': '142 142 142',
|
||||
'--color-background-500': '162 163 163',
|
||||
'--color-background-600': '213 212 212',
|
||||
'--color-background-700': '229 228 228',
|
||||
'--color-background-800': '242 241 241',
|
||||
'--color-background-900': '246 246 246',
|
||||
'--color-background-950': '255 255 255',
|
||||
|
||||
/* Background Special */
|
||||
'--color-background-error': '66 43 43',
|
||||
'--color-background-warning': '65 47 35',
|
||||
'--color-background-success': '28 43 33',
|
||||
'--color-background-muted': '51 51 51',
|
||||
'--color-background-info': '26 40 46',
|
||||
|
||||
/* Focus Ring Indicator */
|
||||
'--color-indicator-primary': '247 247 247',
|
||||
'--color-indicator-info': '161 199 245',
|
||||
'--color-indicator-error': '232 70 69',
|
||||
}),
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {},
|
||||
"devDependencies": {},
|
||||
"components": []
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// This is a Next.js 15 compatible version of the GluestackUIProvider
|
||||
'use client';
|
||||
import React, { useEffect, useLayoutEffect } from 'react';
|
||||
import { config } from './config';
|
||||
import { OverlayProvider } from '@gluestack-ui/core/overlay/creator';
|
||||
import { ToastProvider } from '@gluestack-ui/core/toast/creator';
|
||||
import { setFlushStyles } from '@gluestack-ui/utils/nativewind-utils';
|
||||
import { script } from './script';
|
||||
|
||||
const variableStyleTagId = 'nativewind-style';
|
||||
const createStyle = (styleTagId: string) => {
|
||||
const style = document.createElement('style');
|
||||
style.id = styleTagId;
|
||||
style.appendChild(document.createTextNode(''));
|
||||
return style;
|
||||
};
|
||||
|
||||
export const useSafeLayoutEffect =
|
||||
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
|
||||
export function GluestackUIProvider({
|
||||
mode = 'light',
|
||||
...props
|
||||
}: {
|
||||
mode?: 'light' | 'dark' | 'system';
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
let cssVariablesWithMode = ``;
|
||||
Object.keys(config).forEach((configKey) => {
|
||||
cssVariablesWithMode +=
|
||||
configKey === 'dark' ? `\n .dark {\n ` : `\n:root {\n`;
|
||||
const cssVariables = Object.keys(
|
||||
config[configKey as keyof typeof config]
|
||||
).reduce((acc: string, curr: string) => {
|
||||
acc += `${curr}:${config[configKey as keyof typeof config][curr]}; `;
|
||||
return acc;
|
||||
}, '');
|
||||
cssVariablesWithMode += `${cssVariables} \n}`;
|
||||
});
|
||||
|
||||
setFlushStyles(cssVariablesWithMode);
|
||||
|
||||
const handleMediaQuery = React.useCallback((e: MediaQueryListEvent) => {
|
||||
script(e.matches ? 'dark' : 'light');
|
||||
}, []);
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
if (mode !== 'system') {
|
||||
const documentElement = document.documentElement;
|
||||
if (documentElement) {
|
||||
documentElement.classList.add(mode);
|
||||
documentElement.classList.remove(mode === 'light' ? 'dark' : 'light');
|
||||
documentElement.style.colorScheme = mode;
|
||||
}
|
||||
}
|
||||
}, [mode]);
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
if (mode !== 'system') return;
|
||||
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
media.addListener(handleMediaQuery);
|
||||
|
||||
return () => media.removeListener(handleMediaQuery);
|
||||
}, [handleMediaQuery]);
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const documentElement = document.documentElement;
|
||||
if (documentElement) {
|
||||
const head = documentElement.querySelector('head');
|
||||
let style = head?.querySelector(`[id='${variableStyleTagId}']`);
|
||||
if (!style) {
|
||||
style = createStyle(variableStyleTagId);
|
||||
style.innerHTML = cssVariablesWithMode;
|
||||
if (head) head.appendChild(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<OverlayProvider>
|
||||
<ToastProvider>{props.children}</ToastProvider>
|
||||
</OverlayProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import type { ViewProps } from 'react-native'
|
||||
import { OverlayProvider } from '@gluestack-ui/core/overlay/creator'
|
||||
import { ToastProvider } from '@gluestack-ui/core/toast/creator'
|
||||
import { useColorScheme } from 'nativewind'
|
||||
import * as React from 'react'
|
||||
import { useEffect } from 'react'
|
||||
import { View } from 'react-native'
|
||||
import { config } from './config'
|
||||
|
||||
export type ModeType = 'light' | 'dark' | 'system'
|
||||
|
||||
export function GluestackUIProvider({
|
||||
mode = 'light',
|
||||
...props
|
||||
}: {
|
||||
mode?: ModeType
|
||||
children?: React.ReactNode
|
||||
style?: ViewProps['style']
|
||||
}) {
|
||||
const { colorScheme, setColorScheme } = useColorScheme()
|
||||
|
||||
useEffect(() => {
|
||||
setColorScheme(mode)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [mode])
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
config[colorScheme!],
|
||||
{ flex: 1, height: '100%', width: '100%' },
|
||||
props.style,
|
||||
]}
|
||||
>
|
||||
<OverlayProvider>
|
||||
<ToastProvider>{props.children}</ToastProvider>
|
||||
</OverlayProvider>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
'use client';
|
||||
import React, { useEffect, useLayoutEffect } from 'react';
|
||||
import { config } from './config';
|
||||
import { OverlayProvider } from '@gluestack-ui/core/overlay/creator';
|
||||
import { ToastProvider } from '@gluestack-ui/core/toast/creator';
|
||||
import { setFlushStyles } from '@gluestack-ui/utils/nativewind-utils';
|
||||
import { script } from './script';
|
||||
|
||||
export type ModeType = 'light' | 'dark' | 'system';
|
||||
|
||||
const variableStyleTagId = 'nativewind-style';
|
||||
const createStyle = (styleTagId: string) => {
|
||||
const style = document.createElement('style');
|
||||
style.id = styleTagId;
|
||||
style.appendChild(document.createTextNode(''));
|
||||
return style;
|
||||
};
|
||||
|
||||
export const useSafeLayoutEffect =
|
||||
typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
||||
|
||||
export function GluestackUIProvider({
|
||||
mode = 'light',
|
||||
...props
|
||||
}: {
|
||||
mode?: ModeType;
|
||||
children?: React.ReactNode;
|
||||
}) {
|
||||
let cssVariablesWithMode = ``;
|
||||
Object.keys(config).forEach((configKey) => {
|
||||
cssVariablesWithMode +=
|
||||
configKey === 'dark' ? `\n .dark {\n ` : `\n:root {\n`;
|
||||
const cssVariables = Object.keys(
|
||||
config[configKey as keyof typeof config]
|
||||
).reduce((acc: string, curr: string) => {
|
||||
acc += `${curr}:${config[configKey as keyof typeof config][curr]}; `;
|
||||
return acc;
|
||||
}, '');
|
||||
cssVariablesWithMode += `${cssVariables} \n}`;
|
||||
});
|
||||
|
||||
setFlushStyles(cssVariablesWithMode);
|
||||
|
||||
const handleMediaQuery = React.useCallback((e: MediaQueryListEvent) => {
|
||||
script(e.matches ? 'dark' : 'light');
|
||||
}, []);
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
if (mode !== 'system') {
|
||||
const documentElement = document.documentElement;
|
||||
if (documentElement) {
|
||||
documentElement.classList.add(mode);
|
||||
documentElement.classList.remove(mode === 'light' ? 'dark' : 'light');
|
||||
documentElement.style.colorScheme = mode;
|
||||
}
|
||||
}
|
||||
}, [mode]);
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
if (mode !== 'system') return;
|
||||
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
|
||||
media.addListener(handleMediaQuery);
|
||||
|
||||
return () => media.removeListener(handleMediaQuery);
|
||||
}, [handleMediaQuery]);
|
||||
|
||||
useSafeLayoutEffect(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
const documentElement = document.documentElement;
|
||||
if (documentElement) {
|
||||
const head = documentElement.querySelector('head');
|
||||
let style = head?.querySelector(`[id='${variableStyleTagId}']`);
|
||||
if (!style) {
|
||||
style = createStyle(variableStyleTagId);
|
||||
style.innerHTML = cssVariablesWithMode;
|
||||
if (head) head.appendChild(style);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
suppressHydrationWarning
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: `(${script.toString()})('${mode}')`,
|
||||
}}
|
||||
/>
|
||||
<OverlayProvider>
|
||||
<ToastProvider>{props.children}</ToastProvider>
|
||||
</OverlayProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export const script = (mode: string) => {
|
||||
const documentElement = document.documentElement;
|
||||
|
||||
function getSystemColorMode() {
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
: 'light';
|
||||
}
|
||||
|
||||
try {
|
||||
const isSystem = mode === 'system';
|
||||
const theme = isSystem ? getSystemColorMode() : mode;
|
||||
documentElement.classList.remove(theme === 'light' ? 'dark' : 'light');
|
||||
documentElement.classList.add(theme);
|
||||
documentElement.style.colorScheme = theme;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
@@ -1,24 +1,20 @@
|
||||
import antfu from '@antfu/eslint-config'
|
||||
// eslint.config.mjs
|
||||
import expoConfig from 'eslint-config-expo/flat.js'
|
||||
|
||||
export default [
|
||||
...expoConfig,
|
||||
...antfu({
|
||||
formatters: true,
|
||||
react: true,
|
||||
stylistic: {
|
||||
indent: 2, // 缩进为 2 个空格(可选 4 或 'tab')
|
||||
quotes: 'single', // 使用单引号(可选 'double')
|
||||
},
|
||||
rules: {
|
||||
'unused-imports/no-unused-imports': 'warn',
|
||||
'unused-imports/no-unused-vars': 'warn',
|
||||
'@typescript-eslint/no-use-before-define': 'off', // 关闭 TS 的“定义前使用”警告
|
||||
'no-console': 'off', // 关闭 console 报错
|
||||
'node/prefer-global/process': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'no-useless-catch': 'off',
|
||||
},
|
||||
}),
|
||||
]
|
||||
export default antfu({
|
||||
formatters: true,
|
||||
react: true,
|
||||
stylistic: {
|
||||
indent: 2, // 缩进为 2 个空格(可选 4 或 'tab')
|
||||
quotes: 'single', // 使用单引号(可选 'double')
|
||||
},
|
||||
rules: {
|
||||
'unused-imports/no-unused-imports': 'off',
|
||||
'unused-imports/no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off', // 关闭 TS 的“定义前使用”警告
|
||||
'no-console': 'off', // 关闭 console 报错
|
||||
'node/prefer-global/process': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'no-useless-catch': 'off',
|
||||
'react-refresh/only-export-components': 'off',
|
||||
},
|
||||
})
|
||||
@@ -1,21 +0,0 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useColorScheme as useRNColorScheme } from 'react-native';
|
||||
|
||||
/**
|
||||
* To support static rendering, this value needs to be re-calculated on the client side for web
|
||||
*/
|
||||
export function useColorScheme() {
|
||||
const [hasHydrated, setHasHydrated] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setHasHydrated(true);
|
||||
}, []);
|
||||
|
||||
const colorScheme = useRNColorScheme();
|
||||
|
||||
if (hasHydrated) {
|
||||
return colorScheme;
|
||||
}
|
||||
|
||||
return 'light';
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Learn more https://docs.expo.io/guides/customizing-metro
|
||||
const { getDefaultConfig } = require('expo/metro-config');
|
||||
const { getDefaultConfig } = require('expo/metro-config')
|
||||
const { withNativeWind } = require('nativewind/metro')
|
||||
|
||||
/** @type {import('expo/metro-config').MetroConfig} */
|
||||
const config = getDefaultConfig(__dirname);
|
||||
const config = getDefaultConfig(__dirname)
|
||||
|
||||
module.exports = config;
|
||||
module.exports = withNativeWind(config, { input: './styles/global.css' })
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
/// <reference types="nativewind/types" />
|
||||
|
||||
// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.
|
||||
@@ -13,6 +13,10 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@expo/vector-icons": "^15.0.3",
|
||||
"@gluestack-ui/core": "^3.0.16",
|
||||
"@gluestack-ui/utils": "^3.0.19",
|
||||
"@gluestack/ui-next-adapter": "^3.0.3",
|
||||
"@react-native-async-storage/async-storage": "^3.0.2",
|
||||
"@react-navigation/bottom-tabs": "^7.4.0",
|
||||
"@react-navigation/elements": "^2.6.3",
|
||||
"@react-navigation/native": "^7.1.8",
|
||||
@@ -28,24 +32,32 @@
|
||||
"expo-symbols": "~1.0.8",
|
||||
"expo-system-ui": "~6.0.9",
|
||||
"expo-web-browser": "~15.0.10",
|
||||
"nativewind": "^4.2.3",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-native": "0.81.5",
|
||||
"react-native-css-interop": "^0.2.3",
|
||||
"react-native-gesture-handler": "~2.28.0",
|
||||
"react-native-reanimated": "~4.1.1",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
"react-native-reanimated": "~4.1.7",
|
||||
"react-native-safe-area-context": "~5.6.2",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-svg": "^15.12.1",
|
||||
"react-native-web": "~0.21.0",
|
||||
"react-native-worklets": "0.5.1"
|
||||
"react-native-worklets": "0.5.1",
|
||||
"sonner-native": "^0.24.0",
|
||||
"zustand": "^5.0.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^8.0.0",
|
||||
"@eslint-react/eslint-plugin": "^3.0.0",
|
||||
"@types/react": "~19.1.0",
|
||||
"babel-preset-expo": "~54.0.10",
|
||||
"eslint": "^9.25.0",
|
||||
"eslint-config-expo": "~10.0.0",
|
||||
"eslint-plugin-format": "^2.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"prettier-plugin-tailwindcss": "0.5.11",
|
||||
"tailwindcss": "3.4.17",
|
||||
"typescript": "~5.9.2"
|
||||
}
|
||||
}
|
||||
Generated
+2737
-232
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,64 @@
|
||||
import type { ColorSchemeName } from 'react-native'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
import { Appearance } from 'react-native'
|
||||
import { create } from 'zustand'
|
||||
import { createJSONStorage, persist } from 'zustand/middleware'
|
||||
|
||||
export type ThemeScheme = 'light' | 'dark' | 'auto'
|
||||
|
||||
interface AppState {
|
||||
themeScheme: ThemeScheme
|
||||
isDark: boolean
|
||||
|
||||
setThemeScheme: (theme: ThemeScheme) => void
|
||||
syncWithSystem: () => void
|
||||
}
|
||||
|
||||
function getIsDark(theme: ThemeScheme, system: ColorSchemeName) {
|
||||
if (theme === 'auto') {
|
||||
return system === 'dark'
|
||||
}
|
||||
return theme === 'dark'
|
||||
}
|
||||
|
||||
export const useAppStore = create<AppState>()(
|
||||
persist(
|
||||
(set, get) => {
|
||||
const system = Appearance.getColorScheme()
|
||||
|
||||
return {
|
||||
themeScheme: 'auto',
|
||||
|
||||
isDark: getIsDark('auto', system),
|
||||
|
||||
setThemeScheme: (theme) => {
|
||||
const system = Appearance.getColorScheme()
|
||||
|
||||
set({
|
||||
themeScheme: theme,
|
||||
isDark: getIsDark(theme, system),
|
||||
})
|
||||
},
|
||||
|
||||
syncWithSystem: () => {
|
||||
const { themeScheme } = get()
|
||||
const system = Appearance.getColorScheme()
|
||||
|
||||
if (themeScheme === 'auto') {
|
||||
set({
|
||||
isDark: getIsDark(themeScheme, system),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'app-storage',
|
||||
partialize: state => ({
|
||||
themeScheme: state.themeScheme,
|
||||
}),
|
||||
|
||||
storage: createJSONStorage(() => AsyncStorage),
|
||||
},
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,3 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -0,0 +1,207 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
darkMode: process.env.DARK_MODE ? process.env.DARK_MODE : 'class',
|
||||
content: [
|
||||
'./app/**/*.{html,js,jsx,ts,tsx,mdx}',
|
||||
'./components/**/*.{html,js,jsx,ts,tsx,mdx}',
|
||||
'./utils/**/*.{html,js,jsx,ts,tsx,mdx}',
|
||||
'./*.{html,js,jsx,ts,tsx,mdx}',
|
||||
'./src/**/*.{html,js,jsx,ts,tsx,mdx}',
|
||||
],
|
||||
presets: [require('nativewind/preset')],
|
||||
important: 'html',
|
||||
safelist: [
|
||||
{
|
||||
pattern:
|
||||
// eslint-disable-next-line regexp/no-dupe-disjunctions
|
||||
/(bg|border|text|stroke|fill)-(primary|secondary|tertiary|error|success|warning|info|typography|outline|background|indicator)-(0|50|100|200|300|400|500|600|700|800|900|950|white|gray|black|error|warning|muted|success|info|light|dark|primary)/,
|
||||
},
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
0: 'rgb(var(--color-primary-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-primary-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-primary-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-primary-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-primary-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-primary-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-primary-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-primary-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-primary-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-primary-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-primary-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-primary-950)/<alpha-value>)',
|
||||
},
|
||||
secondary: {
|
||||
0: 'rgb(var(--color-secondary-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-secondary-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-secondary-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-secondary-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-secondary-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-secondary-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-secondary-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-secondary-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-secondary-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-secondary-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-secondary-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-secondary-950)/<alpha-value>)',
|
||||
},
|
||||
tertiary: {
|
||||
50: 'rgb(var(--color-tertiary-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-tertiary-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-tertiary-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-tertiary-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-tertiary-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-tertiary-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-tertiary-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-tertiary-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-tertiary-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-tertiary-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-tertiary-950)/<alpha-value>)',
|
||||
},
|
||||
error: {
|
||||
0: 'rgb(var(--color-error-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-error-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-error-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-error-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-error-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-error-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-error-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-error-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-error-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-error-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-error-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-error-950)/<alpha-value>)',
|
||||
},
|
||||
success: {
|
||||
0: 'rgb(var(--color-success-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-success-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-success-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-success-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-success-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-success-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-success-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-success-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-success-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-success-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-success-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-success-950)/<alpha-value>)',
|
||||
},
|
||||
warning: {
|
||||
0: 'rgb(var(--color-warning-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-warning-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-warning-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-warning-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-warning-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-warning-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-warning-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-warning-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-warning-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-warning-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-warning-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-warning-950)/<alpha-value>)',
|
||||
},
|
||||
info: {
|
||||
0: 'rgb(var(--color-info-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-info-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-info-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-info-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-info-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-info-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-info-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-info-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-info-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-info-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-info-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-info-950)/<alpha-value>)',
|
||||
},
|
||||
typography: {
|
||||
0: 'rgb(var(--color-typography-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-typography-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-typography-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-typography-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-typography-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-typography-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-typography-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-typography-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-typography-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-typography-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-typography-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-typography-950)/<alpha-value>)',
|
||||
white: '#FFFFFF',
|
||||
gray: '#D4D4D4',
|
||||
black: '#181718',
|
||||
},
|
||||
outline: {
|
||||
0: 'rgb(var(--color-outline-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-outline-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-outline-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-outline-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-outline-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-outline-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-outline-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-outline-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-outline-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-outline-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-outline-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-outline-950)/<alpha-value>)',
|
||||
},
|
||||
background: {
|
||||
0: 'rgb(var(--color-background-0)/<alpha-value>)',
|
||||
50: 'rgb(var(--color-background-50)/<alpha-value>)',
|
||||
100: 'rgb(var(--color-background-100)/<alpha-value>)',
|
||||
200: 'rgb(var(--color-background-200)/<alpha-value>)',
|
||||
300: 'rgb(var(--color-background-300)/<alpha-value>)',
|
||||
400: 'rgb(var(--color-background-400)/<alpha-value>)',
|
||||
500: 'rgb(var(--color-background-500)/<alpha-value>)',
|
||||
600: 'rgb(var(--color-background-600)/<alpha-value>)',
|
||||
700: 'rgb(var(--color-background-700)/<alpha-value>)',
|
||||
800: 'rgb(var(--color-background-800)/<alpha-value>)',
|
||||
900: 'rgb(var(--color-background-900)/<alpha-value>)',
|
||||
950: 'rgb(var(--color-background-950)/<alpha-value>)',
|
||||
error: 'rgb(var(--color-background-error)/<alpha-value>)',
|
||||
warning: 'rgb(var(--color-background-warning)/<alpha-value>)',
|
||||
muted: 'rgb(var(--color-background-muted)/<alpha-value>)',
|
||||
success: 'rgb(var(--color-background-success)/<alpha-value>)',
|
||||
info: 'rgb(var(--color-background-info)/<alpha-value>)',
|
||||
light: '#FBFBFB',
|
||||
dark: '#181719',
|
||||
},
|
||||
indicator: {
|
||||
primary: 'rgb(var(--color-indicator-primary)/<alpha-value>)',
|
||||
info: 'rgb(var(--color-indicator-info)/<alpha-value>)',
|
||||
error: 'rgb(var(--color-indicator-error)/<alpha-value>)',
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
'heading': undefined,
|
||||
'body': undefined,
|
||||
'mono': undefined,
|
||||
'jakarta': ['var(--font-plus-jakarta-sans)'],
|
||||
'roboto': ['var(--font-roboto)'],
|
||||
'code': ['var(--font-source-code-pro)'],
|
||||
'inter': ['var(--font-inter)'],
|
||||
'space-mono': ['var(--font-space-mono)'],
|
||||
},
|
||||
fontWeight: {
|
||||
extrablack: '950',
|
||||
},
|
||||
fontSize: {
|
||||
'2xs': '10px',
|
||||
},
|
||||
boxShadow: {
|
||||
'hard-1': '-2px 2px 8px 0px rgba(38, 38, 38, 0.20)',
|
||||
'hard-2': '0px 3px 10px 0px rgba(38, 38, 38, 0.20)',
|
||||
'hard-3': '2px 2px 8px 0px rgba(38, 38, 38, 0.20)',
|
||||
'hard-4': '0px -3px 10px 0px rgba(38, 38, 38, 0.20)',
|
||||
'hard-5': '0px 2px 10px 0px rgba(38, 38, 38, 0.10)',
|
||||
'soft-1': '0px 0px 10px rgba(38, 38, 38, 0.1)',
|
||||
'soft-2': '0px 0px 20px rgba(38, 38, 38, 0.2)',
|
||||
'soft-3': '0px 0px 30px rgba(38, 38, 38, 0.1)',
|
||||
'soft-4': '0px 0px 40px rgba(38, 38, 38, 0.1)',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".expo/types/**/*.ts",
|
||||
"expo-env.d.ts"
|
||||
"expo-env.d.ts",
|
||||
"nativewind-env.d.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
declare namespace App {
|
||||
type ThemeScheme = 'light' | 'dark' | 'auto'
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
export {}
|
||||
|
||||
declare global {
|
||||
type ThemeScheme = 'light' | 'dark' | 'auto'
|
||||
}
|
||||
@@ -64,6 +64,8 @@ async function loadModLevelList() {
|
||||
soption: '',
|
||||
}).then((res) => {
|
||||
modLevelList.value = res.data
|
||||
|
||||
console.log(modLevelList.value)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -134,6 +136,7 @@ async function queryList(pageNo: number, pageSize: number) {
|
||||
if (mlvSearch) {
|
||||
sql = `${sql} and (${mlvSearch})`
|
||||
}
|
||||
|
||||
if (searchValue.value) {
|
||||
sql = `${sql} and b_id like '%${searchValue.value}%'`
|
||||
}
|
||||
@@ -148,6 +151,7 @@ async function queryList(pageNo: number, pageSize: number) {
|
||||
})
|
||||
.then((res) => {
|
||||
const { rows, total } = res.data
|
||||
console.log(rows)
|
||||
|
||||
let idList = []
|
||||
rows.forEach((row) => {
|
||||
@@ -186,6 +190,7 @@ async function queryList(pageNo: number, pageSize: number) {
|
||||
|
||||
row.b_module_id = bModuleId
|
||||
}
|
||||
|
||||
for (const modLevel of modLevelList.value) {
|
||||
if (modLevel.b_module_id === bModuleId) {
|
||||
slevel = modLevel.b_level
|
||||
@@ -202,6 +207,7 @@ async function queryList(pageNo: number, pageSize: number) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (canbh || cansh) {
|
||||
if (cansh) {
|
||||
row.tongGuoButton = true
|
||||
@@ -219,7 +225,7 @@ async function queryList(pageNo: number, pageSize: number) {
|
||||
}
|
||||
})
|
||||
|
||||
if (rows) {
|
||||
if (rows2) {
|
||||
pagingRef.value.complete(rows2)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -197,17 +197,18 @@ function formatDateTime(dateTime: string): string {
|
||||
<LoadingPage v-if="loading" />
|
||||
|
||||
<view v-else-if="stepsData.length > 0" class="">
|
||||
<view style="display: flex;align-items: center;">
|
||||
<text style="font-size: 28rpx; margin-right: 8rpx;">
|
||||
当前进度 -
|
||||
<view style="display: flex;flex-direction:column;align-items: center;justify-content: center;width: 100%;">
|
||||
<text style="font-size: 28rpx;margin-bottom: 16rpx;font-weight: 500;">
|
||||
当前进度
|
||||
</text>
|
||||
|
||||
<view style="flex:1">
|
||||
<view style="flex:1;width: 100%;">
|
||||
<sar-progress-bar
|
||||
:percent="computedProgress"
|
||||
inside
|
||||
striped
|
||||
animated
|
||||
thickness="38rpx"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"g3hd": "http://192.168.1.137:8084/g3hy2024-app/h5"
|
||||
"g3hd": "http://192.168.1.137:8085/g3hy2024-app/h5"
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
import { computed, ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { getModuleQxApi } from '@/request/api'
|
||||
import { useUserStore } from './user'
|
||||
|
||||
@@ -12,7 +12,7 @@ const PERMISSION_PATH_MAP = [
|
||||
{
|
||||
moduleId: 'car_daily',
|
||||
path: '/pages-sub/baoxiao',
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
export const usePermissionStore = defineStore(
|
||||
|
||||
Reference in new issue
Block a user