commit c6154273f2a89a0976a01482fee04334e945a8f8 Author: admin Date: Mon Apr 13 07:20:09 2026 +0800 Initial commit: 商品售卖网站 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ec6a6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Dependencies +node_modules/ +vendor/ + +# Build outputs +frontend/dist/ +backend/sale.db +backend/sale + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Env files +.env +.env.local +.env.*.local + +# Logs +*.log +npm-debug.log* + +# Temp files +*.tmp +*.temp diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go new file mode 100644 index 0000000..576f375 --- /dev/null +++ b/backend/cmd/server/main.go @@ -0,0 +1,32 @@ +package main + +import ( + "log" + + "sale/internal/api/routes" + "sale/internal/config" + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +func main() { + config.Load() + + utils.InitDB() + utils.AutoMigrate() + + if config.AppConfig.Server.Mode == "release" { + gin.SetMode(gin.ReleaseMode) + } + + r := gin.Default() + + routes.SetupRoutes(r) + + addr := ":" + config.AppConfig.Server.Port + log.Printf("Server starting on %s", addr) + if err := r.Run(addr); err != nil { + log.Fatalf("Failed to start server: %v", err) + } +} diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..28201cb --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,62 @@ +module sale + +go 1.26.2 + +require ( + github.com/gin-gonic/gin v1.12.0 + github.com/glebarez/sqlite v1.11.0 + github.com/golang-jwt/jwt/v5 v5.3.1 + github.com/joho/godotenv v1.5.1 + golang.org/x/crypto v0.50.0 + gorm.io/driver/mysql v1.6.0 + gorm.io/driver/postgres v1.6.0 + gorm.io/gorm v1.31.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/glebarez/go-sqlite v1.21.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect + golang.org/x/arch v0.22.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + modernc.org/libc v1.22.5 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/sqlite v1.23.1 // indirect +) diff --git a/backend/go.sum b/backend/go.sum new file mode 100644 index 0000000..26c801f --- /dev/null +++ b/backend/go.sum @@ -0,0 +1,139 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= +github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo= +github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +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.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.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.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +go.mongodb.org/mongo-driver/v2 v2.5.0 h1:yXUhImUjjAInNcpTcAlPHiT7bIXhshCTL3jVBkF3xaE= +go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +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.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM= +modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= diff --git a/backend/internal/api/handlers/address.go b/backend/internal/api/handlers/address.go new file mode 100644 index 0000000..c27c49b --- /dev/null +++ b/backend/internal/api/handlers/address.go @@ -0,0 +1,124 @@ +package handlers + +import ( + "net/http" + "strconv" + + "sale/internal/models" + "sale/internal/schemas" + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +type AddressHandler struct{} + +func NewAddressHandler() *AddressHandler { + return &AddressHandler{} +} + +func (h *AddressHandler) List(c *gin.Context) { + userID := c.GetUint("user_id") + var addresses []models.Address + utils.DB.Where("user_id = ?", userID).Order("is_default DESC, created_at DESC").Find(&addresses) + c.JSON(http.StatusOK, gin.H{"data": addresses}) +} + +func (h *AddressHandler) Create(c *gin.Context) { + userID := c.GetUint("user_id") + var req schemas.CreateAddressRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if req.IsDefault { + utils.DB.Model(&models.Address{}).Where("user_id = ?", userID).Update("is_default", false) + } + + address := models.Address{ + UserID: userID, + Name: req.Name, + Phone: req.Phone, + Province: req.Province, + City: req.City, + District: req.District, + Address: req.Address, + IsDefault: req.IsDefault, + } + + utils.DB.Create(&address) + c.JSON(http.StatusCreated, gin.H{"data": address}) +} + +func (h *AddressHandler) Update(c *gin.Context) { + userID := c.GetUint("user_id") + id, _ := strconv.Atoi(c.Param("id")) + + var address models.Address + if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&address).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Address not found"}) + return + } + + var req schemas.UpdateAddressRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + updates := make(map[string]interface{}) + if req.Name != nil { + updates["name"] = *req.Name + } + if req.Phone != nil { + updates["phone"] = *req.Phone + } + if req.Province != nil { + updates["province"] = *req.Province + } + if req.City != nil { + updates["city"] = *req.City + } + if req.District != nil { + updates["district"] = *req.District + } + if req.Address != nil { + updates["address"] = *req.Address + } + if req.IsDefault != nil && *req.IsDefault { + utils.DB.Model(&models.Address{}).Where("user_id = ?", userID).Update("is_default", false) + updates["is_default"] = true + } + + utils.DB.Model(&address).Updates(updates) + c.JSON(http.StatusOK, gin.H{"data": address}) +} + +func (h *AddressHandler) Delete(c *gin.Context) { + userID := c.GetUint("user_id") + id, _ := strconv.Atoi(c.Param("id")) + + if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).Delete(&models.Address{}).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete address"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Address deleted successfully"}) +} + +func (h *AddressHandler) SetDefault(c *gin.Context) { + userID := c.GetUint("user_id") + id, _ := strconv.Atoi(c.Param("id")) + + var address models.Address + if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&address).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Address not found"}) + return + } + + utils.DB.Model(&models.Address{}).Where("user_id = ?", userID).Update("is_default", false) + utils.DB.Model(&address).Update("is_default", true) + + c.JSON(http.StatusOK, gin.H{"message": "Default address set successfully"}) +} diff --git a/backend/internal/api/handlers/article.go b/backend/internal/api/handlers/article.go new file mode 100644 index 0000000..d1f5315 --- /dev/null +++ b/backend/internal/api/handlers/article.go @@ -0,0 +1,164 @@ +package handlers + +import ( + "math" + "net/http" + "strconv" + + "sale/internal/models" + "sale/internal/schemas" + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +type ArticleHandler struct{} + +func NewArticleHandler() *ArticleHandler { + return &ArticleHandler{} +} + +func (h *ArticleHandler) List(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) + + var total int64 + utils.DB.Model(&models.Article{}).Where("is_published = ?", true).Count(&total) + + var articles []models.Article + offset := (page - 1) * pageSize + utils.DB.Where("is_published = ?", true). + Order("is_pinned DESC, sort_order ASC, created_at DESC"). + Offset(offset).Limit(pageSize).Find(&articles) + + totalPages := int(math.Ceil(float64(total) / float64(pageSize))) + c.JSON(http.StatusOK, gin.H{ + "data": articles, + "pagination": gin.H{ + "page": page, + "page_size": pageSize, + "total": total, + "total_pages": totalPages, + }, + }) +} + +func (h *ArticleHandler) GetByID(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var article models.Article + if err := utils.DB.Preload("Author").First(&article, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Article not found"}) + return + } + c.JSON(http.StatusOK, gin.H{"data": article}) +} + +func (h *ArticleHandler) AdminList(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) + + var total int64 + utils.DB.Model(&models.Article{}).Count(&total) + + var articles []models.Article + offset := (page - 1) * pageSize + utils.DB.Preload("Author"). + Order("is_pinned DESC, sort_order ASC, created_at DESC"). + Offset(offset).Limit(pageSize).Find(&articles) + + totalPages := int(math.Ceil(float64(total) / float64(pageSize))) + c.JSON(http.StatusOK, gin.H{ + "data": articles, + "pagination": gin.H{ + "page": page, + "page_size": pageSize, + "total": total, + "total_pages": totalPages, + }, + }) +} + +func (h *ArticleHandler) Create(c *gin.Context) { + userID := c.GetUint("user_id") + var req schemas.CreateArticleRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + article := models.Article{ + Title: req.Title, + Content: req.Content, + Summary: req.Summary, + CoverImage: req.CoverImage, + IsPinned: req.IsPinned, + IsPublished: req.IsPublished, + SortOrder: req.SortOrder, + AuthorID: &userID, + } + + utils.DB.Create(&article) + c.JSON(http.StatusCreated, gin.H{"data": article}) +} + +func (h *ArticleHandler) Update(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var article models.Article + if err := utils.DB.First(&article, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Article not found"}) + return + } + + var req schemas.UpdateArticleRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + updates := make(map[string]interface{}) + if req.Title != nil { + updates["title"] = *req.Title + } + if req.Content != nil { + updates["content"] = *req.Content + } + if req.Summary != nil { + updates["summary"] = *req.Summary + } + if req.CoverImage != nil { + updates["cover_image"] = *req.CoverImage + } + if req.IsPinned != nil { + updates["is_pinned"] = *req.IsPinned + } + if req.IsPublished != nil { + updates["is_published"] = *req.IsPublished + } + if req.SortOrder != nil { + updates["sort_order"] = *req.SortOrder + } + + utils.DB.Model(&article).Updates(updates) + c.JSON(http.StatusOK, gin.H{"data": article}) +} + +func (h *ArticleHandler) Delete(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + if err := utils.DB.Delete(&models.Article{}, id).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete article"}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Article deleted successfully"}) +} + +func (h *ArticleHandler) TogglePin(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var article models.Article + if err := utils.DB.First(&article, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Article not found"}) + return + } + + utils.DB.Model(&article).Update("is_pinned", !article.IsPinned) + c.JSON(http.StatusOK, gin.H{"data": article}) +} diff --git a/backend/internal/api/handlers/auth.go b/backend/internal/api/handlers/auth.go new file mode 100644 index 0000000..f165db1 --- /dev/null +++ b/backend/internal/api/handlers/auth.go @@ -0,0 +1,322 @@ +package handlers + +import ( + "net/http" + "time" + + "sale/internal/models" + "sale/internal/schemas" + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +type AuthHandler struct{} + +func NewAuthHandler() *AuthHandler { + return &AuthHandler{} +} + +func (h *AuthHandler) Register(c *gin.Context) { + var req schemas.RegisterRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var existingUser models.User + if err := utils.DB.Where("email = ?", req.Email).First(&existingUser).Error; err == nil { + c.JSON(http.StatusConflict, gin.H{"error": "Email already registered"}) + return + } + + if err := utils.DB.Where("username = ?", req.Username).First(&existingUser).Error; err == nil { + c.JSON(http.StatusConflict, gin.H{"error": "Username already taken"}) + return + } + + hashedPassword, err := utils.HashPassword(req.Password) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"}) + return + } + + user := models.User{ + Username: req.Username, + Email: req.Email, + PasswordHash: hashedPassword, + Role: "user", + InviteCode: utils.GenerateInviteCode(), + IsActive: true, + } + + if req.InviteCode != "" { + var referrer models.User + if err := utils.DB.Where("invite_code = ?", req.InviteCode).First(&referrer).Error; err == nil { + user.ReferredBy = &referrer.ID + } + } + + if err := utils.DB.Create(&user).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"}) + return + } + + if user.ReferredBy != nil { + utils.DB.Model(&models.User{}).Where("id = ?", *user.ReferredBy). + UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + 1")) + } + + verifyCode := utils.GenerateVerifyCode() + utils.SendVerifyEmail(req.Email, verifyCode) + + token, _ := utils.GenerateToken(user.ID, user.Role) + c.JSON(http.StatusCreated, gin.H{ + "token": token, + "user": schemas.UserResponse{ + ID: user.ID, + Username: user.Username, + Email: user.Email, + Role: user.Role, + PurchaseCredits: user.PurchaseCredits, + InviteCode: user.InviteCode, + EmailVerified: user.EmailVerified, + }, + }) +} + +func (h *AuthHandler) Login(c *gin.Context) { + var req schemas.LoginRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var user models.User + if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"}) + return + } + + if !utils.CheckPassword(req.Password, user.PasswordHash) { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid email or password"}) + return + } + + if !user.IsActive { + c.JSON(http.StatusForbidden, gin.H{"error": "Account is disabled"}) + return + } + + token, _ := utils.GenerateToken(user.ID, user.Role) + c.JSON(http.StatusOK, gin.H{ + "token": token, + "user": schemas.UserResponse{ + ID: user.ID, + Username: user.Username, + Email: user.Email, + Role: user.Role, + PurchaseCredits: user.PurchaseCredits, + InviteCode: user.InviteCode, + EmailVerified: user.EmailVerified, + }, + }) +} + +func (h *AuthHandler) ForgotPassword(c *gin.Context) { + var req schemas.ForgotPasswordRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var user models.User + if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil { + c.JSON(http.StatusOK, gin.H{"message": "If the email exists, a verification code has been sent"}) + return + } + + code := utils.GenerateVerifyCode() + utils.SendResetPasswordEmail(req.Email, code) + + c.JSON(http.StatusOK, gin.H{"message": "If the email exists, a verification code has been sent"}) +} + +func (h *AuthHandler) ResetPassword(c *gin.Context) { + var req schemas.ResetPasswordRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var user models.User + if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid email"}) + return + } + + hashedPassword, err := utils.HashPassword(req.Password) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"}) + return + } + + utils.DB.Model(&user).Update("password_hash", hashedPassword) + c.JSON(http.StatusOK, gin.H{"message": "Password reset successfully"}) +} + +func (h *AuthHandler) VerifyEmail(c *gin.Context) { + var req schemas.VerifyEmailRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var user models.User + if err := utils.DB.Where("email = ?", req.Email).First(&user).Error; err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "User not found"}) + return + } + + utils.DB.Model(&user).Update("email_verified", true) + c.JSON(http.StatusOK, gin.H{"message": "Email verified successfully"}) +} + +func (h *AuthHandler) GetProfile(c *gin.Context) { + userID := c.GetUint("user_id") + var user models.User + if err := utils.DB.First(&user, userID).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + + c.JSON(http.StatusOK, schemas.UserResponse{ + ID: user.ID, + Username: user.Username, + Email: user.Email, + Role: user.Role, + PurchaseCredits: user.PurchaseCredits, + InviteCode: user.InviteCode, + EmailVerified: user.EmailVerified, + }) +} + +func (h *AuthHandler) UpdateProfile(c *gin.Context) { + userID := c.GetUint("user_id") + var user models.User + if err := utils.DB.First(&user, userID).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + + var updateData map[string]interface{} + if err := c.ShouldBindJSON(&updateData); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if username, ok := updateData["username"].(string); ok && username != "" { + user.Username = username + } + + utils.DB.Save(&user) + c.JSON(http.StatusOK, gin.H{"message": "Profile updated successfully"}) +} + +func (h *AuthHandler) ChangePassword(c *gin.Context) { + userID := c.GetUint("user_id") + var req schemas.ChangePasswordRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var user models.User + if err := utils.DB.First(&user, userID).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + + if !utils.CheckPassword(req.OldPassword, user.PasswordHash) { + c.JSON(http.StatusBadRequest, gin.H{"error": "Old password is incorrect"}) + return + } + + hashedPassword, _ := utils.HashPassword(req.NewPassword) + utils.DB.Model(&user).Update("password_hash", hashedPassword) + c.JSON(http.StatusOK, gin.H{"message": "Password changed successfully"}) +} + +func (h *AuthHandler) SendVerifyCode(c *gin.Context) { + userID := c.GetUint("user_id") + var user models.User + if err := utils.DB.First(&user, userID).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + + if user.EmailVerified { + c.JSON(http.StatusBadRequest, gin.H{"error": "Email already verified"}) + return + } + + code := utils.GenerateVerifyCode() + utils.SendVerifyEmail(user.Email, code) + + c.JSON(http.StatusOK, gin.H{"message": "Verification code sent", "expires_at": time.Now().Add(30 * time.Minute)}) +} + +func (h *AuthHandler) CheckInstalled(c *gin.Context) { + var count int64 + utils.DB.Model(&models.User{}).Where("role = ?", "admin").Count(&count) + c.JSON(http.StatusOK, gin.H{"installed": count > 0}) +} + +func (h *AuthHandler) Install(c *gin.Context) { + var count int64 + utils.DB.Model(&models.User{}).Where("role = ?", "admin").Count(&count) + if count > 0 { + c.JSON(http.StatusForbidden, gin.H{"error": "System already installed"}) + return + } + + var req schemas.InstallRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + hashedPassword, err := utils.HashPassword(req.Password) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to hash password"}) + return + } + + admin := models.User{ + Username: req.Username, + Email: req.Email, + PasswordHash: hashedPassword, + Role: "admin", + InviteCode: utils.GenerateInviteCode(), + IsActive: true, + } + + if err := utils.DB.Create(&admin).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create admin user"}) + return + } + + token, _ := utils.GenerateToken(admin.ID, admin.Role) + c.JSON(http.StatusCreated, gin.H{ + "token": token, + "user": schemas.UserResponse{ + ID: admin.ID, + Username: admin.Username, + Email: admin.Email, + Role: admin.Role, + PurchaseCredits: admin.PurchaseCredits, + InviteCode: admin.InviteCode, + EmailVerified: admin.EmailVerified, + }, + }) +} diff --git a/backend/internal/api/handlers/brand.go b/backend/internal/api/handlers/brand.go new file mode 100644 index 0000000..e501a1e --- /dev/null +++ b/backend/internal/api/handlers/brand.go @@ -0,0 +1,53 @@ +package handlers + +import ( + "net/http" + "sale/internal/models" + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +type BrandHandler struct{} + +func NewBrandHandler() *BrandHandler { + return &BrandHandler{} +} + +func (h *BrandHandler) List(c *gin.Context) { + var brands []models.Brand + utils.DB.Where("deleted_at IS NULL").Order("sort_order ASC, name ASC").Find(&brands) + c.JSON(http.StatusOK, gin.H{"data": brands}) +} + +func (h *BrandHandler) Create(c *gin.Context) { + var brand models.Brand + if err := c.ShouldBindJSON(&brand); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + utils.DB.Create(&brand) + c.JSON(http.StatusOK, brand) +} + +func (h *BrandHandler) Update(c *gin.Context) { + id := c.Param("id") + var brand models.Brand + if err := utils.DB.First(&brand, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Brand not found"}) + return + } + var input models.Brand + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + utils.DB.Model(&brand).Updates(input) + c.JSON(http.StatusOK, brand) +} + +func (h *BrandHandler) Delete(c *gin.Context) { + id := c.Param("id") + utils.DB.Delete(&models.Brand{}, id) + c.JSON(http.StatusOK, gin.H{"message": "Brand deleted"}) +} diff --git a/backend/internal/api/handlers/lottery.go b/backend/internal/api/handlers/lottery.go new file mode 100644 index 0000000..8b7dc9d --- /dev/null +++ b/backend/internal/api/handlers/lottery.go @@ -0,0 +1,263 @@ +package handlers + +import ( + "math/rand" + "net/http" + "strconv" + "time" + + "sale/internal/models" + "sale/internal/schemas" + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +type LotteryHandler struct{} + +func NewLotteryHandler() *LotteryHandler { + return &LotteryHandler{} +} + +func (h *LotteryHandler) List(c *gin.Context) { + var lotteries []models.Lottery + utils.DB.Where("is_active = ?", true).Preload("Prizes").Order("created_at DESC").Find(&lotteries) + c.JSON(http.StatusOK, gin.H{"data": lotteries}) +} + +func (h *LotteryHandler) GetByID(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var lottery models.Lottery + if err := utils.DB.Preload("Prizes").First(&lottery, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"}) + return + } + c.JSON(http.StatusOK, gin.H{"data": lottery}) +} + +func (h *LotteryHandler) Register(c *gin.Context) { + userID := c.GetUint("user_id") + id, _ := strconv.Atoi(c.Param("id")) + + var lottery models.Lottery + if err := utils.DB.First(&lottery, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"}) + return + } + + now := time.Now() + if now.Before(lottery.StartTime) || now.After(lottery.EndTime) { + c.JSON(http.StatusBadRequest, gin.H{"error": "Lottery is not in registration period"}) + return + } + + var existing models.LotteryParticipant + if err := utils.DB.Where("lottery_id = ? AND user_id = ?", id, userID).First(&existing).Error; err == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Already registered"}) + return + } + + var totalSpent float64 + utils.DB.Model(&models.Order{}). + Where("user_id = ? AND status = ?", userID, models.OrderStatusCompleted). + Select("COALESCE(SUM(total_amount), 0)"). + Scan(&totalSpent) + + participant := models.LotteryParticipant{ + LotteryID: lottery.ID, + UserID: userID, + PurchaseWeight: int(totalSpent), + } + + utils.DB.Create(&participant) + c.JSON(http.StatusOK, gin.H{"message": "Registered successfully"}) +} + +func (h *LotteryHandler) Create(c *gin.Context) { + var req schemas.CreateLotteryRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + startTime, _ := time.Parse(time.RFC3339, req.StartTime) + endTime, _ := time.Parse(time.RFC3339, req.EndTime) + + lottery := models.Lottery{ + Name: req.Name, + Description: req.Description, + StartTime: startTime, + EndTime: endTime, + Cycle: req.Cycle, + DailyQuota: req.DailyQuota, + TotalQuota: req.TotalQuota, + RegistrationValidity: req.RegistrationValidity, + IsActive: true, + } + + utils.DB.Create(&lottery) + c.JSON(http.StatusCreated, gin.H{"data": lottery}) +} + +func (h *LotteryHandler) Update(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var lottery models.Lottery + if err := utils.DB.First(&lottery, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"}) + return + } + + var req schemas.UpdateLotteryRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + updates := make(map[string]interface{}) + if req.Name != nil { + updates["name"] = *req.Name + } + if req.Description != nil { + updates["description"] = *req.Description + } + if req.StartTime != nil { + t, _ := time.Parse(time.RFC3339, *req.StartTime) + updates["start_time"] = t + } + if req.EndTime != nil { + t, _ := time.Parse(time.RFC3339, *req.EndTime) + updates["end_time"] = t + } + if req.Cycle != nil { + updates["cycle"] = *req.Cycle + } + if req.DailyQuota != nil { + updates["daily_quota"] = *req.DailyQuota + } + if req.TotalQuota != nil { + updates["total_quota"] = *req.TotalQuota + } + if req.RegistrationValidity != nil { + updates["registration_validity"] = *req.RegistrationValidity + } + if req.IsActive != nil { + updates["is_active"] = *req.IsActive + } + + utils.DB.Model(&lottery).Updates(updates) + c.JSON(http.StatusOK, gin.H{"data": lottery}) +} + +func (h *LotteryHandler) Delete(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + utils.DB.Delete(&models.Lottery{}, id) + c.JSON(http.StatusOK, gin.H{"message": "Lottery deleted successfully"}) +} + +func (h *LotteryHandler) AddPrize(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var req schemas.AddLotteryPrizeRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + prize := models.LotteryPrize{ + LotteryID: uint(id), + Name: req.Name, + Type: req.Type, + Quantity: req.Quantity, + Weight: req.Weight, + CreditReward: req.CreditReward, + DrawMode: req.DrawMode, + } + + utils.DB.Create(&prize) + c.JSON(http.StatusCreated, gin.H{"data": prize}) +} + +func (h *LotteryHandler) Draw(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var lottery models.Lottery + if err := utils.DB.Preload("Prizes").First(&lottery, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Lottery not found"}) + return + } + + var participants []models.LotteryParticipant + utils.DB.Where("lottery_id = ?", id).Find(&participants) + + if len(participants) == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "No participants"}) + return + } + + var winners []models.LotteryWinner + rng := rand.New(rand.NewSource(time.Now().UnixNano())) + + for _, prize := range lottery.Prizes { + remaining := prize.Quantity + + var weightParticipants []models.LotteryParticipant + var randomParticipants []models.LotteryParticipant + + if prize.DrawMode == models.DrawModeWeight { + weightParticipants = participants + } else { + randomParticipants = participants + } + + selectedParticipants := weightParticipants + if len(selectedParticipants) == 0 { + selectedParticipants = randomParticipants + } + + totalWeight := 0 + for _, p := range selectedParticipants { + w := p.PurchaseWeight + if w < 1 { + w = 1 + } + totalWeight += w + } + + selected := make(map[uint]bool) + for remaining > 0 && len(selected) < len(selectedParticipants) { + r := rng.Intn(totalWeight) + cumWeight := 0 + for _, p := range selectedParticipants { + if selected[p.UserID] { + continue + } + w := p.PurchaseWeight + if w < 1 { + w = 1 + } + cumWeight += w + if cumWeight > r { + winner := models.LotteryWinner{ + LotteryID: lottery.ID, + PrizeID: prize.ID, + UserID: p.UserID, + DrawnAt: time.Now(), + } + winners = append(winners, winner) + selected[p.UserID] = true + remaining-- + + if prize.Type == models.PrizeTypeCredit && prize.CreditReward != nil { + utils.DB.Model(&models.User{}).Where("id = ?", p.UserID). + UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", *prize.CreditReward)) + } + break + } + } + } + } + + if len(winners) > 0 { + utils.DB.Create(&winners) + } + + c.JSON(http.StatusOK, gin.H{"data": winners, "message": "Draw completed successfully"}) +} diff --git a/backend/internal/api/handlers/order.go b/backend/internal/api/handlers/order.go new file mode 100644 index 0000000..f4105de --- /dev/null +++ b/backend/internal/api/handlers/order.go @@ -0,0 +1,414 @@ +package handlers + +import ( + "math" + "net/http" + "strconv" + + "sale/internal/models" + "sale/internal/schemas" + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +type CartHandler struct{} + +func NewCartHandler() *CartHandler { + return &CartHandler{} +} + +func (h *CartHandler) List(c *gin.Context) { + userID := c.GetUint("user_id") + var carts []models.Cart + utils.DB.Where("user_id = ?", userID).Preload("Product").Find(&carts) + c.JSON(http.StatusOK, gin.H{"data": carts}) +} + +func (h *CartHandler) Add(c *gin.Context) { + userID := c.GetUint("user_id") + var req schemas.AddToCartRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var cart models.Cart + result := utils.DB.Where("user_id = ? AND product_id = ?", userID, req.ProductID).First(&cart) + if result.Error == nil { + cart.Quantity += req.Quantity + utils.DB.Save(&cart) + } else { + cart = models.Cart{ + UserID: userID, + ProductID: req.ProductID, + Quantity: req.Quantity, + } + utils.DB.Create(&cart) + } + + utils.DB.Preload("Product").First(&cart, cart.ID) + c.JSON(http.StatusOK, gin.H{"data": cart}) +} + +func (h *CartHandler) Update(c *gin.Context) { + userID := c.GetUint("user_id") + id, _ := strconv.Atoi(c.Param("id")) + + var cart models.Cart + if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&cart).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Cart item not found"}) + return + } + + var req schemas.UpdateCartRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + cart.Quantity = req.Quantity + utils.DB.Save(&cart) + utils.DB.Preload("Product").First(&cart, cart.ID) + c.JSON(http.StatusOK, gin.H{"data": cart}) +} + +func (h *CartHandler) Delete(c *gin.Context) { + userID := c.GetUint("user_id") + id, _ := strconv.Atoi(c.Param("id")) + + if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).Delete(&models.Cart{}).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete cart item"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Cart item deleted successfully"}) +} + +type OrderHandler struct{} + +func NewOrderHandler() *OrderHandler { + return &OrderHandler{} +} + +func (h *OrderHandler) List(c *gin.Context) { + userID := c.GetUint("user_id") + role, _ := c.Get("role") + + var orders []models.Order + query := utils.DB.Model(&models.Order{}) + + if role == "user" { + query = query.Where("user_id = ?", userID) + } else if role == "supplier" { + query = query.Where("supplier_id = ?", userID) + } + + query.Preload("OrderItems.Product").Preload("ShippingAddress"). + Order("created_at DESC").Find(&orders) + + c.JSON(http.StatusOK, gin.H{"data": orders}) +} + +func (h *OrderHandler) GetByID(c *gin.Context) { + userID := c.GetUint("user_id") + role, _ := c.Get("role") + id, _ := strconv.Atoi(c.Param("id")) + + var order models.Order + query := utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User") + + if role == "user" { + query = query.Where("user_id = ?", userID) + } else if role == "supplier" { + query = query.Where("supplier_id = ?", userID) + } + + if err := query.First(&order, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": order}) +} + +func (h *OrderHandler) Create(c *gin.Context) { + userID := c.GetUint("user_id") + var req schemas.CreateOrderRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + var carts []models.Cart + utils.DB.Where("user_id = ?", userID).Preload("Product").Find(&carts) + + if len(carts) == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "Cart is empty"}) + return + } + + var subtotal float64 + var totalQuantity int + var orderItems []models.OrderItem + supplierMap := make(map[uint]bool) + + for _, cart := range carts { + if cart.Product.RequireCredit { + var user models.User + utils.DB.First(&user, userID) + if user.PurchaseCredits < cart.Product.CreditCost*cart.Quantity { + c.JSON(http.StatusBadRequest, gin.H{"error": "Insufficient purchase credits for " + cart.Product.Name}) + return + } + } + + var inventory models.Inventory + if err := utils.DB.Where("product_id = ?", cart.ProductID).First(&inventory).Error; err == nil { + if inventory.Quantity < cart.Quantity { + c.JSON(http.StatusBadRequest, gin.H{"error": "Insufficient stock for " + cart.Product.Name}) + return + } + } + + subtotal += cart.Product.Price * float64(cart.Quantity) + totalQuantity += cart.Quantity + orderItems = append(orderItems, models.OrderItem{ + ProductID: cart.ProductID, + Quantity: cart.Quantity, + Price: cart.Product.Price, + }) + + if inventory.SupplierID != 0 { + supplierMap[inventory.SupplierID] = true + } + } + + var supplierID *uint + for sid := range supplierMap { + sid := sid + supplierID = &sid + break + } + + var shippingFeeFirstWeight, shippingFeePerGram, serviceFeeRate, taxRate float64 + var setting models.SystemSetting + if err := utils.DB.Where("`key` = ?", "shipping_fee_first_weight").First(&setting).Error; err == nil { + shippingFeeFirstWeight, _ = strconv.ParseFloat(setting.Value, 64) + } + if err := utils.DB.Where("`key` = ?", "shipping_fee_per_gram").First(&setting).Error; err == nil { + shippingFeePerGram, _ = strconv.ParseFloat(setting.Value, 64) + } + if err := utils.DB.Where("`key` = ?", "service_fee_rate").First(&setting).Error; err == nil { + serviceFeeRate, _ = strconv.ParseFloat(setting.Value, 64) + } + if err := utils.DB.Where("`key` = ?", "tax_rate").First(&setting).Error; err == nil { + taxRate, _ = strconv.ParseFloat(setting.Value, 64) + } + + shippingFee := shippingFeeFirstWeight + if subtotal >= 99 || shippingFeeFirstWeight == 0 { + shippingFee = 0 + } else if totalQuantity > 500 { + shippingFee += shippingFeePerGram * float64(totalQuantity-500) + } + + serviceFee := subtotal * serviceFeeRate / 100 + tax := subtotal * taxRate / 100 + totalAmount := subtotal + shippingFee + serviceFee + tax + + order := models.Order{ + UserID: userID, + SupplierID: supplierID, + Subtotal: subtotal, + ShippingFee: shippingFee, + ServiceFee: serviceFee, + Tax: tax, + TotalAmount: totalAmount, + Status: models.OrderStatusPendingPayment, + ShippingAddressID: &req.ShippingAddressID, + PaymentMethod: req.PaymentMethod, + } + + if err := utils.DB.Create(&order).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create order"}) + return + } + + for i := range orderItems { + orderItems[i].OrderID = order.ID + } + utils.DB.Create(&orderItems) + + for _, cart := range carts { + if cart.Product.RequireCredit { + utils.DB.Model(&models.User{}).Where("id = ?", userID). + UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits - ?", cart.Product.CreditCost*cart.Quantity)) + } + if cart.Product.CreditReward > 0 { + utils.DB.Model(&models.User{}).Where("id = ?", userID). + UpdateColumn("purchase_credits", utils.DB.Raw("purchase_credits + ?", cart.Product.CreditReward*cart.Quantity)) + } + } + + utils.DB.Where("user_id = ?", userID).Delete(&models.Cart{}) + + utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").First(&order, order.ID) + c.JSON(http.StatusCreated, gin.H{"data": order}) +} + +func (h *OrderHandler) Refund(c *gin.Context) { + userID := c.GetUint("user_id") + id, _ := strconv.Atoi(c.Param("id")) + + var order models.Order + if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&order).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) + return + } + + if order.Status == models.OrderStatusShipped || order.Status == models.OrderStatusCompleted { + c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot refund a shipped or completed order"}) + return + } + + if order.RefundStatus == models.RefundStatusPending { + c.JSON(http.StatusBadRequest, gin.H{"error": "Refund already requested"}) + return + } + + var req schemas.RefundRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + utils.DB.Model(&order).Updates(map[string]interface{}{ + "refund_status": models.RefundStatusPending, + "refund_reason": req.Reason, + "status": models.OrderStatusRefunding, + }) + + c.JSON(http.StatusOK, gin.H{"message": "Refund request submitted successfully"}) +} + +func (h *OrderHandler) ConfirmOrder(c *gin.Context) { + userID := c.GetUint("user_id") + id, _ := strconv.Atoi(c.Param("id")) + + var order models.Order + if err := utils.DB.Where("id = ? AND supplier_id = ?", id, userID).First(&order).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) + return + } + + if order.Status != models.OrderStatusPendingConfirm { + c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be confirmed"}) + return + } + + utils.DB.Model(&order).Update("status", models.OrderStatusPendingShip) + c.JSON(http.StatusOK, gin.H{"message": "Order confirmed successfully"}) +} + +func (h *OrderHandler) ShipOrder(c *gin.Context) { + userID := c.GetUint("user_id") + id, _ := strconv.Atoi(c.Param("id")) + + var order models.Order + if err := utils.DB.Where("id = ? AND supplier_id = ?", id, userID).First(&order).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) + return + } + + if order.Status != models.OrderStatusPendingShip { + c.JSON(http.StatusBadRequest, gin.H{"error": "Order cannot be shipped"}) + return + } + + var req schemas.ShipOrderRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + utils.DB.Model(&order).Updates(map[string]interface{}{ + "status": models.OrderStatusShipped, + "tracking_number": req.TrackingNumber, + "shipping_photo": req.ShippingPhoto, + "express_photo": req.ExpressPhoto, + "customs_photo": req.CustomsPhoto, + }) + + c.JSON(http.StatusOK, gin.H{"message": "Order shipped successfully"}) +} + +func (h *OrderHandler) AdminList(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) + + var total int64 + utils.DB.Model(&models.Order{}).Count(&total) + + var orders []models.Order + offset := (page - 1) * pageSize + utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User"). + Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&orders) + + totalPages := int(math.Ceil(float64(total) / float64(pageSize))) + c.JSON(http.StatusOK, gin.H{ + "data": orders, + "pagination": gin.H{ + "page": page, + "page_size": pageSize, + "total": total, + "total_pages": totalPages, + }, + }) +} + +func (h *OrderHandler) ProcessRefund(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + + var order models.Order + if err := utils.DB.First(&order, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Order not found"}) + return + } + + var req schemas.ProcessRefundRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if req.Status == "approved" { + var setting models.SystemSetting + feeRate := 0.0 + if err := utils.DB.Where("`key` = ?", "payment_channel_fee_rate").First(&setting).Error; err == nil { + feeRate, _ = strconv.ParseFloat(setting.Value, 64) + } + + refundAmount := order.TotalAmount * (1 - feeRate/100) + utils.DB.Model(&order).Updates(map[string]interface{}{ + "refund_status": models.RefundStatusCompleted, + "refund_amount": refundAmount, + "status": models.OrderStatusRefunded, + }) + } else { + utils.DB.Model(&order).Updates(map[string]interface{}{ + "refund_status": models.RefundStatusRejected, + "status": models.OrderStatusPendingConfirm, + }) + } + + c.JSON(http.StatusOK, gin.H{"message": "Refund processed successfully"}) +} + +func (h *OrderHandler) Export(c *gin.Context) { + var orders []models.Order + utils.DB.Preload("OrderItems.Product").Preload("ShippingAddress").Preload("User"). + Order("created_at DESC").Find(&orders) + + c.JSON(http.StatusOK, gin.H{"data": orders}) +} diff --git a/backend/internal/api/handlers/product.go b/backend/internal/api/handlers/product.go new file mode 100644 index 0000000..d519dd6 --- /dev/null +++ b/backend/internal/api/handlers/product.go @@ -0,0 +1,356 @@ +package handlers + +import ( + "math" + "net/http" + "strconv" + + "sale/internal/models" + "sale/internal/schemas" + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +type CategoryHandler struct{} + +func NewCategoryHandler() *CategoryHandler { + return &CategoryHandler{} +} + +func (h *CategoryHandler) List(c *gin.Context) { + var categories []models.Category + utils.DB.Where("parent_id IS NULL").Order("sort_order ASC, id ASC").Find(&categories) + for i := range categories { + utils.DB.Where("parent_id = ?", categories[i].ID).Order("sort_order ASC, id ASC").Find(&categories[i].Children) + } + c.JSON(http.StatusOK, gin.H{"data": categories}) +} + +func (h *CategoryHandler) Create(c *gin.Context) { + var req schemas.CreateCategoryRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + category := models.Category{ + Name: req.Name, + Description: req.Description, + ParentID: req.ParentID, + MinAmount: req.MinAmount, + MaxAmount: req.MaxAmount, + MinQuantity: req.MinQuantity, + MaxQuantity: req.MaxQuantity, + MinWeight: req.MinWeight, + MaxWeight: req.MaxWeight, + SortOrder: req.SortOrder, + } + + if err := utils.DB.Create(&category).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create category"}) + return + } + + c.JSON(http.StatusCreated, gin.H{"data": category}) +} + +func (h *CategoryHandler) Update(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var category models.Category + if err := utils.DB.First(&category, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Category not found"}) + return + } + + var req schemas.UpdateCategoryRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + updates := make(map[string]interface{}) + if req.Name != nil { + updates["name"] = *req.Name + } + if req.Description != nil { + updates["description"] = *req.Description + } + if req.ParentID != nil { + updates["parent_id"] = *req.ParentID + } + if req.MinAmount != nil { + updates["min_amount"] = *req.MinAmount + } + if req.MaxAmount != nil { + updates["max_amount"] = *req.MaxAmount + } + if req.MinQuantity != nil { + updates["min_quantity"] = *req.MinQuantity + } + if req.MaxQuantity != nil { + updates["max_quantity"] = *req.MaxQuantity + } + if req.MinWeight != nil { + updates["min_weight"] = *req.MinWeight + } + if req.MaxWeight != nil { + updates["max_weight"] = *req.MaxWeight + } + if req.SortOrder != nil { + updates["sort_order"] = *req.SortOrder + } + + utils.DB.Model(&category).Updates(updates) + c.JSON(http.StatusOK, gin.H{"data": category}) +} + +func (h *CategoryHandler) Delete(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + if err := utils.DB.Delete(&models.Category{}, id).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete category"}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Category deleted successfully"}) +} + +type ProductHandler struct{} + +func NewProductHandler() *ProductHandler { + return &ProductHandler{} +} + +func (h *ProductHandler) List(c *gin.Context) { + var req schemas.ProductListRequest + if err := c.ShouldBindQuery(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + query := utils.DB.Model(&models.Product{}).Where("is_active = ?", true) + + if req.CategoryID != nil { + query = query.Joins("JOIN product_categories ON product_categories.product_id = products.id"). + Where("product_categories.category_id = ?", *req.CategoryID) + } + + if req.BrandID != nil { + query = query.Where("brand_id = ?", *req.BrandID) + } + + if req.Keyword != "" { + query = query.Where("name ILIKE ?", "%"+req.Keyword+"%") + } + + if req.MinPrice != nil { + query = query.Where("price >= ?", *req.MinPrice) + } + + if req.MaxPrice != nil { + query = query.Where("price <= ?", *req.MaxPrice) + } + + var total int64 + query.Count(&total) + + var products []models.Product + offset := (req.Page - 1) * req.PageSize + query.Preload("Categories").Preload("CustomFields").Preload("Brand"). + Order("created_at DESC"). + Offset(offset).Limit(req.PageSize). + Find(&products) + + totalPages := int(math.Ceil(float64(total) / float64(req.PageSize))) + c.JSON(http.StatusOK, gin.H{ + "data": products, + "pagination": gin.H{ + "page": req.Page, + "page_size": req.PageSize, + "total": total, + "total_pages": totalPages, + }, + }) +} + +func (h *ProductHandler) GetByID(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var product models.Product + if err := utils.DB.Preload("Categories").Preload("CustomFields").First(&product, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"}) + return + } + c.JSON(http.StatusOK, gin.H{"data": product}) +} + +func (h *ProductHandler) AdminList(c *gin.Context) { + var products []models.Product + utils.DB.Preload("Categories").Preload("Brand").Order("created_at DESC").Find(&products) + c.JSON(http.StatusOK, gin.H{"data": products}) +} + +func (h *ProductHandler) Create(c *gin.Context) { + var req schemas.CreateProductRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + product := models.Product{ + Name: req.Name, + Description: req.Description, + Price: req.Price, + MinPurchase: req.MinPurchase, + MaxPurchase: req.MaxPurchase, + MinWeight: req.MinWeight, + MaxWeight: req.MaxWeight, + MinAmount: req.MinAmount, + MaxAmount: req.MaxAmount, + RequireCredit: req.RequireCredit, + CreditCost: req.CreditCost, + CreditReward: req.CreditReward, + Images: req.Images, + BrandID: req.BrandID, + IsActive: true, + } + + if err := utils.DB.Create(&product).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create product"}) + return + } + + if len(req.CategoryIDs) > 0 { + var categories []models.Category + utils.DB.Where("id IN ?", req.CategoryIDs).Find(&categories) + utils.DB.Model(&product).Association("Categories").Replace(categories) + } + + if len(req.CustomFields) > 0 { + for _, cf := range req.CustomFields { + utils.DB.Create(&models.ProductCustomField{ + ProductID: product.ID, + FieldName: cf.FieldName, + FieldValue: cf.FieldValue, + }) + } + } + + utils.DB.Preload("Categories").Preload("CustomFields").Preload("Brand").First(&product, product.ID) + c.JSON(http.StatusCreated, gin.H{"data": product}) +} + +func (h *ProductHandler) Update(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var product models.Product + if err := utils.DB.First(&product, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"}) + return + } + + var req schemas.UpdateProductRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + updates := make(map[string]interface{}) + if req.Name != nil { + updates["name"] = *req.Name + } + if req.Description != nil { + updates["description"] = *req.Description + } + if req.Price != nil { + updates["price"] = *req.Price + } + if req.MinPurchase != nil { + updates["min_purchase"] = *req.MinPurchase + } + if req.MaxPurchase != nil { + updates["max_purchase"] = *req.MaxPurchase + } + if req.MinWeight != nil { + updates["min_weight"] = *req.MinWeight + } + if req.MaxWeight != nil { + updates["max_weight"] = *req.MaxWeight + } + if req.MinAmount != nil { + updates["min_amount"] = *req.MinAmount + } + if req.MaxAmount != nil { + updates["max_amount"] = *req.MaxAmount + } + if req.RequireCredit != nil { + updates["require_credit"] = *req.RequireCredit + } + if req.CreditCost != nil { + updates["credit_cost"] = *req.CreditCost + } + if req.CreditReward != nil { + updates["credit_reward"] = *req.CreditReward + } + if req.Images != nil { + updates["images"] = *req.Images + } + if req.IsActive != nil { + updates["is_active"] = *req.IsActive + } + if req.BrandID != nil { + updates["brand_id"] = *req.BrandID + } + + utils.DB.Model(&product).Updates(updates) + + if req.CategoryIDs != nil { + var categories []models.Category + utils.DB.Where("id IN ?", req.CategoryIDs).Find(&categories) + utils.DB.Model(&product).Association("Categories").Replace(categories) + } + + if req.CustomFields != nil { + utils.DB.Where("product_id = ?", product.ID).Delete(&models.ProductCustomField{}) + for _, cf := range req.CustomFields { + utils.DB.Create(&models.ProductCustomField{ + ProductID: product.ID, + FieldName: cf.FieldName, + FieldValue: cf.FieldValue, + }) + } + } + + utils.DB.Preload("Categories").Preload("CustomFields").Preload("Brand").First(&product, product.ID) + c.JSON(http.StatusOK, gin.H{"data": product}) +} + +func (h *ProductHandler) Delete(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + if err := utils.DB.Delete(&models.Product{}, id).Error; err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete product"}) + return + } + c.JSON(http.StatusOK, gin.H{"message": "Product deleted successfully"}) +} + +func (h *ProductHandler) AddCustomField(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var product models.Product + if err := utils.DB.First(&product, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Product not found"}) + return + } + + var req schemas.CustomFieldRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + field := models.ProductCustomField{ + ProductID: product.ID, + FieldName: req.FieldName, + FieldValue: req.FieldValue, + } + + utils.DB.Create(&field) + c.JSON(http.StatusCreated, gin.H{"data": field}) +} diff --git a/backend/internal/api/handlers/system.go b/backend/internal/api/handlers/system.go new file mode 100644 index 0000000..7c70287 --- /dev/null +++ b/backend/internal/api/handlers/system.go @@ -0,0 +1,247 @@ +package handlers + +import ( + "net/http" + "strconv" + + "sale/internal/models" + "sale/internal/schemas" + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +type SystemHandler struct{} + +func NewSystemHandler() *SystemHandler { + return &SystemHandler{} +} + +func (h *SystemHandler) GetSettings(c *gin.Context) { + var settings []models.SystemSetting + utils.DB.Find(&settings) + + result := make(map[string]string) + for _, s := range settings { + result[s.Key] = s.Value + } + + c.JSON(http.StatusOK, gin.H{"data": result}) +} + +func (h *SystemHandler) GetPublicSettings(c *gin.Context) { + publicKeys := []string{ + "payment_channel_fee_rate", + "shipping_fee_first_weight", + "shipping_fee_per_gram", + "service_fee_rate", + "tax_rate", + "enabled_payments", + } + + var settings []models.SystemSetting + utils.DB.Where("`key` IN ?", publicKeys).Find(&settings) + + result := make(map[string]string) + for _, s := range settings { + result[s.Key] = s.Value + } + + c.JSON(http.StatusOK, gin.H{"data": result}) +} + +func (h *SystemHandler) GetStats(c *gin.Context) { + var userCount, productCount, orderCount int64 + var totalRevenue float64 + + utils.DB.Model(&models.User{}).Count(&userCount) + utils.DB.Model(&models.Product{}).Count(&productCount) + utils.DB.Model(&models.Order{}).Count(&orderCount) + utils.DB.Model(&models.Order{}).Where("status = ?", "completed").Select("COALESCE(SUM(total), 0)").Scan(&totalRevenue) + + c.JSON(http.StatusOK, gin.H{ + "data": gin.H{ + "users": userCount, + "products": productCount, + "orders": orderCount, + "revenue": totalRevenue, + }, + }) +} + +func (h *SystemHandler) UpdateSettings(c *gin.Context) { + var req schemas.UpdateSettingsRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + for key, value := range req.Settings { + var setting models.SystemSetting + result := utils.DB.Where("`key` = ?", key).First(&setting) + if result.Error != nil { + utils.DB.Create(&models.SystemSetting{Key: key, Value: value}) + } else { + utils.DB.Model(&setting).Update("value", value) + } + } + + c.JSON(http.StatusOK, gin.H{"message": "Settings updated successfully"}) +} + +func (h *SystemHandler) UpdateSMTP(c *gin.Context) { + var req map[string]string + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + smtpKeys := []string{"smtp_host", "smtp_port", "smtp_user", "smtp_password", "smtp_from"} + for _, key := range smtpKeys { + if value, ok := req[key]; ok { + var setting models.SystemSetting + result := utils.DB.Where("`key` = ?", key).First(&setting) + if result.Error != nil { + utils.DB.Create(&models.SystemSetting{Key: key, Value: value}) + } else { + utils.DB.Model(&setting).Update("value", value) + } + } + } + + c.JSON(http.StatusOK, gin.H{"message": "SMTP settings updated successfully"}) +} + +func (h *SystemHandler) UpdatePayment(c *gin.Context) { + var req map[string]string + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + for key, value := range req { + var setting models.SystemSetting + result := utils.DB.Where("`key` = ?", key).First(&setting) + if result.Error != nil { + utils.DB.Create(&models.SystemSetting{Key: key, Value: value}) + } else { + utils.DB.Model(&setting).Update("value", value) + } + } + + c.JSON(http.StatusOK, gin.H{"message": "Payment settings updated successfully"}) +} + +type SupplierHandler struct{} + +func NewSupplierHandler() *SupplierHandler { + return &SupplierHandler{} +} + +func (h *SupplierHandler) List(c *gin.Context) { + var suppliers []models.User + utils.DB.Where("role = ?", "supplier").Find(&suppliers) + c.JSON(http.StatusOK, gin.H{"data": suppliers}) +} + +func (h *SupplierHandler) Create(c *gin.Context) { + var req schemas.AddSupplierRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + hashedPassword, _ := utils.HashPassword(req.Password) + supplier := models.User{ + Username: req.Username, + Email: req.Email, + PasswordHash: hashedPassword, + Role: "supplier", + InviteCode: utils.GenerateInviteCode(), + IsActive: true, + } + + utils.DB.Create(&supplier) + c.JSON(http.StatusCreated, gin.H{"data": supplier}) +} + +func (h *SupplierHandler) Update(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var supplier models.User + if err := utils.DB.Where("id = ? AND role = ?", id, "supplier").First(&supplier).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Supplier not found"}) + return + } + + var updateData map[string]interface{} + if err := c.ShouldBindJSON(&updateData); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + utils.DB.Model(&supplier).Updates(updateData) + c.JSON(http.StatusOK, gin.H{"data": supplier}) +} + +func (h *SupplierHandler) Delete(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + utils.DB.Where("id = ? AND role = ?", id, "supplier").Delete(&models.User{}) + c.JSON(http.StatusOK, gin.H{"message": "Supplier deleted successfully"}) +} + +func (h *SupplierHandler) Authorize(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var req schemas.AuthorizeSupplierRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + auth := models.SupplierAuthorization{ + SupplierID: uint(id), + Type: req.Type, + RefID: req.RefID, + } + + utils.DB.Create(&auth) + c.JSON(http.StatusCreated, gin.H{"data": auth}) +} + +type InventoryHandler struct{} + +func NewInventoryHandler() *InventoryHandler { + return &InventoryHandler{} +} + +func (h *InventoryHandler) SupplierList(c *gin.Context) { + userID := c.GetUint("user_id") + var inventory []models.Inventory + utils.DB.Where("supplier_id = ?", userID).Preload("Product").Find(&inventory) + c.JSON(http.StatusOK, gin.H{"data": inventory}) +} + +func (h *InventoryHandler) Update(c *gin.Context) { + userID := c.GetUint("user_id") + id, _ := strconv.Atoi(c.Param("id")) + + var inventory models.Inventory + if err := utils.DB.Where("id = ? AND supplier_id = ?", id, userID).First(&inventory).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Inventory not found"}) + return + } + + var req schemas.UpdateInventoryRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + utils.DB.Model(&inventory).Update("quantity", req.Quantity) + c.JSON(http.StatusOK, gin.H{"data": inventory}) +} + +func (h *InventoryHandler) AdminList(c *gin.Context) { + var inventory []models.Inventory + utils.DB.Preload("Product").Find(&inventory) + c.JSON(http.StatusOK, gin.H{"data": inventory}) +} diff --git a/backend/internal/api/handlers/ticket.go b/backend/internal/api/handlers/ticket.go new file mode 100644 index 0000000..3d4c911 --- /dev/null +++ b/backend/internal/api/handlers/ticket.go @@ -0,0 +1,166 @@ +package handlers + +import ( + "math" + "net/http" + "strconv" + + "sale/internal/models" + "sale/internal/schemas" + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +type TicketHandler struct{} + +func NewTicketHandler() *TicketHandler { + return &TicketHandler{} +} + +func (h *TicketHandler) List(c *gin.Context) { + userID := c.GetUint("user_id") + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) + + var total int64 + utils.DB.Model(&models.Ticket{}).Where("user_id = ?", userID).Count(&total) + + var tickets []models.Ticket + offset := (page - 1) * pageSize + utils.DB.Where("user_id = ?", userID).Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&tickets) + + totalPages := int(math.Ceil(float64(total) / float64(pageSize))) + c.JSON(http.StatusOK, gin.H{ + "data": tickets, + "pagination": gin.H{ + "page": page, + "page_size": pageSize, + "total": total, + "total_pages": totalPages, + }, + }) +} + +func (h *TicketHandler) Create(c *gin.Context) { + userID := c.GetUint("user_id") + var req schemas.CreateTicketRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + category := req.Category + if category == "" { + category = models.TicketCategoryOther + } + + ticket := models.Ticket{ + UserID: userID, + Title: req.Title, + Content: req.Content, + Category: category, + Status: models.TicketStatusPending, + } + + utils.DB.Create(&ticket) + c.JSON(http.StatusCreated, gin.H{"data": ticket}) +} + +func (h *TicketHandler) GetByID(c *gin.Context) { + userID := c.GetUint("user_id") + id, _ := strconv.Atoi(c.Param("id")) + + var ticket models.Ticket + if err := utils.DB.Where("id = ? AND user_id = ?", id, userID).First(&ticket).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Ticket not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": ticket}) +} + +func (h *TicketHandler) AdminList(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) + status := c.Query("status") + + var total int64 + query := utils.DB.Model(&models.Ticket{}) + if status != "" { + query = query.Where("status = ?", status) + } + query.Count(&total) + + var tickets []models.Ticket + offset := (page - 1) * pageSize + q := utils.DB.Preload("User").Preload("Assignee") + if status != "" { + q = q.Where("status = ?", status) + } + q.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&tickets) + + totalPages := int(math.Ceil(float64(total) / float64(pageSize))) + c.JSON(http.StatusOK, gin.H{ + "data": tickets, + "pagination": gin.H{ + "page": page, + "page_size": pageSize, + "total": total, + "total_pages": totalPages, + }, + }) +} + +func (h *TicketHandler) Update(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var ticket models.Ticket + if err := utils.DB.First(&ticket, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Ticket not found"}) + return + } + + var req schemas.UpdateTicketRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + updates := make(map[string]interface{}) + if req.Status != nil { + updates["status"] = *req.Status + } + if req.AssignedTo != nil { + updates["assigned_to"] = *req.AssignedTo + } + if req.Reply != nil { + updates["reply"] = *req.Reply + } + + utils.DB.Model(&ticket).Updates(updates) + c.JSON(http.StatusOK, gin.H{"data": ticket}) +} + +func (h *TicketHandler) Assign(c *gin.Context) { + id, _ := strconv.Atoi(c.Param("id")) + var ticket models.Ticket + if err := utils.DB.First(&ticket, id).Error; err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Ticket not found"}) + return + } + + var req struct { + AssignedTo uint `json:"assigned_to" binding:"required"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + utils.DB.Model(&ticket).Updates(map[string]interface{}{ + "assigned_to": req.AssignedTo, + "status": models.TicketStatusProcessing, + }) + + c.JSON(http.StatusOK, gin.H{"message": "Ticket assigned successfully"}) +} diff --git a/backend/internal/api/handlers/upload.go b/backend/internal/api/handlers/upload.go new file mode 100644 index 0000000..46a2557 --- /dev/null +++ b/backend/internal/api/handlers/upload.go @@ -0,0 +1,138 @@ +package handlers + +import ( + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/gin-gonic/gin" + "github.com/google/uuid" +) + +type UploadHandler struct{} + +func NewUploadHandler() *UploadHandler { + return &UploadHandler{} +} + +func (h *UploadHandler) UploadImage(c *gin.Context) { + file, err := c.FormFile("file") + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "No file uploaded"}) + return + } + + ext := strings.ToLower(filepath.Ext(file.Filename)) + allowedExts := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true} + if !allowedExts[ext] { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid file type. Only jpg, jpeg, png, gif, webp are allowed"}) + return + } + + uploadDir := "uploads/images" + if err := os.MkdirAll(uploadDir, 0755); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create upload directory"}) + return + } + + filename := fmt.Sprintf("%s%s", uuid.New().String(), ext) + filepath := filepath.Join(uploadDir, filename) + + if err := c.SaveUploadedFile(file, filepath); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save file"}) + return + } + + url := fmt.Sprintf("/uploads/images/%s", filename) + c.JSON(http.StatusOK, gin.H{ + "url": url, + "filename": filename, + }) +} + +func (h *UploadHandler) UploadMultiple(c *gin.Context) { + form, err := c.MultipartForm() + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "No files uploaded"}) + return + } + + files := form.File["files"] + if len(files) == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "No files uploaded"}) + return + } + + uploadDir := "uploads/images" + if err := os.MkdirAll(uploadDir, 0755); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create upload directory"}) + return + } + + allowedExts := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".gif": true, ".webp": true} + var urls []string + + for _, file := range files { + ext := strings.ToLower(filepath.Ext(file.Filename)) + if !allowedExts[ext] { + continue + } + + filename := fmt.Sprintf("%s%s", uuid.New().String(), ext) + filepath := filepath.Join(uploadDir, filename) + + if err := c.SaveUploadedFile(file, filepath); err != nil { + continue + } + + urls = append(urls, fmt.Sprintf("/uploads/images/%s", filename)) + } + + c.JSON(http.StatusOK, gin.H{ + "urls": urls, + }) +} + +func (h *UploadHandler) DeleteImage(c *gin.Context) { + filename := c.Param("filename") + if filename == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Filename is required"}) + return + } + + filepath := filepath.Join("uploads/images", filename) + if err := os.Remove(filepath); err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "File not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "File deleted successfully"}) +} + +func (h *UploadHandler) ExportOrders(c *gin.Context) { + filename := fmt.Sprintf("orders_%s.csv", time.Now().Format("20060102150405")) + filepath := filepath.Join("uploads", filename) + + file, err := os.Create(filepath) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create file"}) + return + } + defer file.Close() + + c.Header("Content-Description", "File Transfer") + c.Header("Content-Transfer-Encoding", "binary") + c.Header("Content-Disposition", "attachment; filename="+filename) + c.Header("Content-Type", "text/csv") + + file.Seek(0, 0) + _, err = io.Copy(c.Writer, file) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to send file"}) + return + } +} diff --git a/backend/internal/api/middlewares/auth.go b/backend/internal/api/middlewares/auth.go new file mode 100644 index 0000000..5add20e --- /dev/null +++ b/backend/internal/api/middlewares/auth.go @@ -0,0 +1,79 @@ +package middlewares + +import ( + "net/http" + "strings" + + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +func AuthMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + authHeader := c.GetHeader("Authorization") + if authHeader == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is required"}) + c.Abort() + return + } + + parts := strings.SplitN(authHeader, " ", 2) + if len(parts) != 2 || parts[0] != "Bearer" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authorization header format"}) + c.Abort() + return + } + + claims, err := utils.ParseToken(parts[1]) + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"}) + c.Abort() + return + } + + c.Set("user_id", claims.UserID) + c.Set("role", claims.Role) + c.Next() + } +} + +func RoleMiddleware(roles ...string) gin.HandlerFunc { + roleMap := make(map[string]bool) + for _, r := range roles { + roleMap[r] = true + } + + return func(c *gin.Context) { + role, exists := c.Get("role") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + + if !roleMap[role.(string)] { + c.JSON(http.StatusForbidden, gin.H{"error": "Permission denied"}) + c.Abort() + return + } + + c.Next() + } +} + +func CORSMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + c.Writer.Header().Set("Access-Control-Allow-Origin", "*") + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") + c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(204) + return + } + + c.Next() + } +} diff --git a/backend/internal/api/routes/routes.go b/backend/internal/api/routes/routes.go new file mode 100644 index 0000000..46d43e0 --- /dev/null +++ b/backend/internal/api/routes/routes.go @@ -0,0 +1,207 @@ +package routes + +import ( + "sale/internal/api/handlers" + "sale/internal/api/middlewares" + "sale/internal/models" + "sale/internal/utils" + + "github.com/gin-gonic/gin" +) + +func SetupRoutes(r *gin.Engine) { + authHandler := handlers.NewAuthHandler() + categoryHandler := handlers.NewCategoryHandler() + brandHandler := handlers.NewBrandHandler() + productHandler := handlers.NewProductHandler() + cartHandler := handlers.NewCartHandler() + orderHandler := handlers.NewOrderHandler() + addressHandler := handlers.NewAddressHandler() + lotteryHandler := handlers.NewLotteryHandler() + ticketHandler := handlers.NewTicketHandler() + systemHandler := handlers.NewSystemHandler() + supplierHandler := handlers.NewSupplierHandler() + inventoryHandler := handlers.NewInventoryHandler() + articleHandler := handlers.NewArticleHandler() + uploadHandler := handlers.NewUploadHandler() + + r.Use(middlewares.CORSMiddleware()) + + r.Static("/uploads", "./uploads") + + api := r.Group("/api") + { + api.GET("/installed", authHandler.CheckInstalled) + api.POST("/install", authHandler.Install) + + auth := api.Group("/auth") + { + auth.POST("/register", authHandler.Register) + auth.POST("/login", authHandler.Login) + auth.POST("/forgot-password", authHandler.ForgotPassword) + auth.POST("/reset-password", authHandler.ResetPassword) + auth.GET("/verify-email", authHandler.VerifyEmail) + } + + articles := api.Group("/articles") + { + articles.GET("", articleHandler.List) + articles.GET("/:id", articleHandler.GetByID) + } + + api.GET("/categories", categoryHandler.List) + api.GET("/brands", brandHandler.List) + api.GET("/products", productHandler.List) + api.GET("/products/:id", productHandler.GetByID) + api.GET("/lotteries", lotteryHandler.List) + api.GET("/lotteries/:id", lotteryHandler.GetByID) + api.GET("/settings/public", systemHandler.GetPublicSettings) + + authed := api.Group("") + authed.Use(middlewares.AuthMiddleware()) + { + authed.POST("/upload", uploadHandler.UploadImage) + authed.POST("/upload/multiple", uploadHandler.UploadMultiple) + authed.DELETE("/upload/:filename", uploadHandler.DeleteImage) + + users := authed.Group("/users") + { + users.GET("/profile", authHandler.GetProfile) + users.PUT("/profile", authHandler.UpdateProfile) + users.PUT("/password", authHandler.ChangePassword) + users.POST("/send-verify-code", authHandler.SendVerifyCode) + } + + addresses := authed.Group("/users/addresses") + { + addresses.GET("", addressHandler.List) + addresses.POST("", addressHandler.Create) + addresses.PUT("/:id", addressHandler.Update) + addresses.DELETE("/:id", addressHandler.Delete) + addresses.PUT("/:id/default", addressHandler.SetDefault) + } + + carts := authed.Group("/cart") + { + carts.GET("", cartHandler.List) + carts.POST("", cartHandler.Add) + carts.PUT("/:id", cartHandler.Update) + carts.DELETE("/:id", cartHandler.Delete) + } + + orders := authed.Group("/orders") + { + orders.GET("", orderHandler.List) + orders.GET("/:id", orderHandler.GetByID) + orders.POST("", orderHandler.Create) + orders.POST("/:id/refund", orderHandler.Refund) + } + + lotteries := authed.Group("/lotteries") + { + lotteries.POST("/:id/register", lotteryHandler.Register) + } + + tickets := authed.Group("/tickets") + { + tickets.GET("", ticketHandler.List) + tickets.POST("", ticketHandler.Create) + tickets.GET("/:id", ticketHandler.GetByID) + } + + supplier := authed.Group("/supplier") + supplier.Use(middlewares.RoleMiddleware("supplier", "admin")) + { + supplier.GET("/orders", orderHandler.List) + supplier.GET("/orders/:id", orderHandler.GetByID) + supplier.PUT("/orders/:id/confirm", orderHandler.ConfirmOrder) + supplier.PUT("/orders/:id/ship", orderHandler.ShipOrder) + supplier.GET("/inventory", inventoryHandler.SupplierList) + supplier.PUT("/inventory/:id", inventoryHandler.Update) + } + + admin := authed.Group("/admin") + admin.Use(middlewares.RoleMiddleware("admin")) + { + admin.GET("/stats", systemHandler.GetStats) + admin.GET("/users", func(c *gin.Context) { + var users []models.User + utils.DB.Select("id, username, email, role, purchase_credits, is_active, created_at").Find(&users) + c.JSON(200, gin.H{"data": users}) + }) + + adminCategories := admin.Group("/categories") + { + adminCategories.POST("", categoryHandler.Create) + adminCategories.PUT("/:id", categoryHandler.Update) + adminCategories.DELETE("/:id", categoryHandler.Delete) + } + + adminBrands := admin.Group("/brands") + { + adminBrands.GET("", brandHandler.List) + adminBrands.POST("", brandHandler.Create) + adminBrands.PUT("/:id", brandHandler.Update) + adminBrands.DELETE("/:id", brandHandler.Delete) + } + + adminProducts := admin.Group("/products") + { + adminProducts.GET("", productHandler.AdminList) + adminProducts.POST("", productHandler.Create) + adminProducts.PUT("/:id", productHandler.Update) + adminProducts.DELETE("/:id", productHandler.Delete) + adminProducts.POST("/:id/custom-fields", productHandler.AddCustomField) + } + + adminOrders := admin.Group("/orders") + { + adminOrders.GET("", orderHandler.AdminList) + adminOrders.GET("/export", orderHandler.Export) + adminOrders.PUT("/:id/refund", orderHandler.ProcessRefund) + } + + adminLotteries := admin.Group("/lotteries") + { + adminLotteries.POST("", lotteryHandler.Create) + adminLotteries.PUT("/:id", lotteryHandler.Update) + adminLotteries.DELETE("/:id", lotteryHandler.Delete) + adminLotteries.POST("/:id/prizes", lotteryHandler.AddPrize) + adminLotteries.POST("/:id/draw", lotteryHandler.Draw) + } + + adminTickets := admin.Group("/tickets") + { + adminTickets.GET("", ticketHandler.AdminList) + adminTickets.PUT("/:id", ticketHandler.Update) + adminTickets.PUT("/:id/assign", ticketHandler.Assign) + } + + admin.GET("/settings", systemHandler.GetSettings) + admin.PUT("/settings", systemHandler.UpdateSettings) + admin.PUT("/settings/smtp", systemHandler.UpdateSMTP) + admin.PUT("/settings/payment", systemHandler.UpdatePayment) + + adminSuppliers := admin.Group("/suppliers") + { + adminSuppliers.GET("", supplierHandler.List) + adminSuppliers.POST("", supplierHandler.Create) + adminSuppliers.PUT("/:id", supplierHandler.Update) + adminSuppliers.DELETE("/:id", supplierHandler.Delete) + adminSuppliers.POST("/:id/authorize", supplierHandler.Authorize) + } + + admin.GET("/inventory", inventoryHandler.AdminList) + + adminArticles := admin.Group("/articles") + { + adminArticles.GET("", articleHandler.AdminList) + adminArticles.POST("", articleHandler.Create) + adminArticles.PUT("/:id", articleHandler.Update) + adminArticles.DELETE("/:id", articleHandler.Delete) + adminArticles.PUT("/:id/pin", articleHandler.TogglePin) + } + } + } + } +} diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go new file mode 100644 index 0000000..7d6e644 --- /dev/null +++ b/backend/internal/config/config.go @@ -0,0 +1,98 @@ +package config + +import ( + "os" + + "github.com/joho/godotenv" +) + +type Config struct { + Server ServerConfig + Database DatabaseConfig + Redis RedisConfig + JWT JWTConfig + SMTP SMTPConfig +} + +type ServerConfig struct { + Port string + Mode string +} + +type DatabaseConfig struct { + Driver string + Host string + Port string + User string + Password string + DBName string + SSLMode string + FilePath string +} + +type RedisConfig struct { + Host string + Port string + Password string + DB int +} + +type JWTConfig struct { + Secret string + ExpireHour int +} + +type SMTPConfig struct { + Host string + Port string + User string + Password string + From string +} + +var AppConfig *Config + +func Load() { + godotenv.Load() + + AppConfig = &Config{ + Server: ServerConfig{ + Port: getEnv("SERVER_PORT", "8080"), + Mode: getEnv("SERVER_MODE", "debug"), + }, + Database: DatabaseConfig{ + Driver: getEnv("DB_DRIVER", "sqlite"), + Host: getEnv("DB_HOST", "localhost"), + Port: getEnv("DB_PORT", "5432"), + User: getEnv("DB_USER", "postgres"), + Password: getEnv("DB_PASSWORD", "postgres"), + DBName: getEnv("DB_NAME", "sale"), + SSLMode: getEnv("DB_SSLMODE", "disable"), + FilePath: getEnv("DB_FILEPATH", "sale.db"), + }, + Redis: RedisConfig{ + Host: getEnv("REDIS_HOST", "localhost"), + Port: getEnv("REDIS_PORT", "6379"), + Password: getEnv("REDIS_PASSWORD", ""), + DB: 0, + }, + JWT: JWTConfig{ + Secret: getEnv("JWT_SECRET", "sale-secret-key"), + ExpireHour: 72, + }, + SMTP: SMTPConfig{ + Host: getEnv("SMTP_HOST", ""), + Port: getEnv("SMTP_PORT", "587"), + User: getEnv("SMTP_USER", ""), + Password: getEnv("SMTP_PASSWORD", ""), + From: getEnv("SMTP_FROM", ""), + }, + } +} + +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} diff --git a/backend/internal/models/address.go b/backend/internal/models/address.go new file mode 100644 index 0000000..40730c8 --- /dev/null +++ b/backend/internal/models/address.go @@ -0,0 +1,23 @@ +package models + +import ( + "time" +) + +type Address struct { + ID uint `gorm:"primaryKey" json:"id"` + UserID uint `gorm:"index;not null" json:"user_id"` + Name string `gorm:"size:50;not null" json:"name"` + Phone string `gorm:"size:20;not null" json:"phone"` + Province string `gorm:"size:50" json:"province"` + City string `gorm:"size:50" json:"city"` + District string `gorm:"size:50" json:"district"` + Address string `gorm:"type:text;not null" json:"address"` + IsDefault bool `gorm:"default:false" json:"is_default"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (Address) TableName() string { + return "addresses" +} diff --git a/backend/internal/models/article.go b/backend/internal/models/article.go new file mode 100644 index 0000000..27631ca --- /dev/null +++ b/backend/internal/models/article.go @@ -0,0 +1,27 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type Article struct { + ID uint `gorm:"primaryKey" json:"id"` + Title string `gorm:"size:255;not null" json:"title"` + Content string `gorm:"type:text;not null" json:"content"` + Summary string `gorm:"size:500" json:"summary"` + CoverImage string `gorm:"size:500" json:"cover_image"` + IsPinned bool `gorm:"default:false" json:"is_pinned"` + IsPublished bool `gorm:"default:true" json:"is_published"` + SortOrder int `gorm:"default:0" json:"sort_order"` + AuthorID *uint `json:"author_id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + Author *User `gorm:"foreignKey:AuthorID" json:"author,omitempty"` +} + +func (Article) TableName() string { + return "articles" +} diff --git a/backend/internal/models/brand.go b/backend/internal/models/brand.go new file mode 100644 index 0000000..2f7b6cf --- /dev/null +++ b/backend/internal/models/brand.go @@ -0,0 +1,22 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type Brand struct { + ID uint `gorm:"primaryKey" json:"id"` + Name string `gorm:"size:255;not null;uniqueIndex" json:"name"` + Description string `json:"description"` + Logo string `json:"logo"` + SortOrder int `gorm:"default:0" json:"sort_order"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +func (Brand) TableName() string { + return "brands" +} diff --git a/backend/internal/models/cart.go b/backend/internal/models/cart.go new file mode 100644 index 0000000..f2f818a --- /dev/null +++ b/backend/internal/models/cart.go @@ -0,0 +1,19 @@ +package models + +import ( + "time" +) + +type Cart struct { + ID uint `gorm:"primaryKey" json:"id"` + UserID uint `gorm:"index;not null" json:"user_id"` + ProductID uint `gorm:"index;not null" json:"product_id"` + Quantity int `gorm:"not null" json:"quantity"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Product Product `json:"product,omitempty"` +} + +func (Cart) TableName() string { + return "carts" +} diff --git a/backend/internal/models/category.go b/backend/internal/models/category.go new file mode 100644 index 0000000..0753222 --- /dev/null +++ b/backend/internal/models/category.go @@ -0,0 +1,29 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type Category struct { + ID uint `gorm:"primaryKey" json:"id"` + Name string `gorm:"size:100;not null" json:"name"` + Description string `json:"description"` + ParentID *uint `json:"parent_id"` + MinAmount *float64 `json:"min_amount"` + MaxAmount *float64 `json:"max_amount"` + MinQuantity *int `json:"min_quantity"` + MaxQuantity *int `json:"max_quantity"` + MinWeight *float64 `json:"min_weight"` + MaxWeight *float64 `json:"max_weight"` + SortOrder int `gorm:"default:0" json:"sort_order"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + Children []Category `gorm:"foreignKey:ParentID" json:"children,omitempty"` +} + +func (Category) TableName() string { + return "categories" +} diff --git a/backend/internal/models/inventory.go b/backend/internal/models/inventory.go new file mode 100644 index 0000000..f7c6bed --- /dev/null +++ b/backend/internal/models/inventory.go @@ -0,0 +1,17 @@ +package models + +import ( + "time" +) + +type Inventory struct { + ID uint `gorm:"primaryKey" json:"id"` + ProductID uint `gorm:"index;not null" json:"product_id"` + SupplierID uint `gorm:"index;not null" json:"supplier_id"` + Quantity int `gorm:"default:0" json:"quantity"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (Inventory) TableName() string { + return "inventory" +} diff --git a/backend/internal/models/lottery.go b/backend/internal/models/lottery.go new file mode 100644 index 0000000..ecdfd09 --- /dev/null +++ b/backend/internal/models/lottery.go @@ -0,0 +1,80 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type Lottery struct { + ID uint `gorm:"primaryKey" json:"id"` + Name string `gorm:"size:100;not null" json:"name"` + Description string `json:"description"` + StartTime time.Time `json:"start_time"` + EndTime time.Time `json:"end_time"` + Cycle string `gorm:"size:20" json:"cycle"` + DailyQuota *int `json:"daily_quota"` + TotalQuota *int `json:"total_quota"` + RegistrationValidity *int `json:"registration_validity"` + IsActive bool `gorm:"default:true" json:"is_active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + Prizes []LotteryPrize `json:"prizes,omitempty"` +} + +func (Lottery) TableName() string { + return "lotteries" +} + +type LotteryPrize struct { + ID uint `gorm:"primaryKey" json:"id"` + LotteryID uint `gorm:"index;not null" json:"lottery_id"` + Name string `gorm:"size:100;not null" json:"name"` + Type string `gorm:"size:20;not null" json:"type"` + Quantity int `gorm:"not null" json:"quantity"` + Weight int `gorm:"default:1" json:"weight"` + CreditReward *int `json:"credit_reward"` + DrawMode string `gorm:"size:20;default:'random'" json:"draw_mode"` +} + +func (LotteryPrize) TableName() string { + return "lottery_prizes" +} + +type LotteryParticipant struct { + ID uint `gorm:"primaryKey" json:"id"` + LotteryID uint `gorm:"index;not null" json:"lottery_id"` + UserID uint `gorm:"index;not null" json:"user_id"` + PurchaseWeight int `gorm:"default:0" json:"purchase_weight"` + RegisteredAt time.Time `json:"registered_at"` +} + +func (LotteryParticipant) TableName() string { + return "lottery_participants" +} + +type LotteryWinner struct { + ID uint `gorm:"primaryKey" json:"id"` + LotteryID uint `gorm:"index;not null" json:"lottery_id"` + PrizeID uint `gorm:"index;not null" json:"prize_id"` + UserID uint `gorm:"index;not null" json:"user_id"` + DrawnAt time.Time `json:"drawn_at"` +} + +func (LotteryWinner) TableName() string { + return "lottery_winners" +} + +const ( + PrizeTypePhysical = "physical" + PrizeTypeVirtual = "virtual" + PrizeTypeCredit = "credit" + + DrawModeRandom = "random" + DrawModeWeight = "weight" + + CycleDaily = "daily" + CycleWeekly = "weekly" + CycleMonthly = "monthly" +) diff --git a/backend/internal/models/order.go b/backend/internal/models/order.go new file mode 100644 index 0000000..6201518 --- /dev/null +++ b/backend/internal/models/order.go @@ -0,0 +1,70 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type Order struct { + ID uint `gorm:"primaryKey" json:"id"` + UserID uint `gorm:"index;not null" json:"user_id"` + SupplierID *uint `gorm:"index" json:"supplier_id"` + Subtotal float64 `gorm:"type:decimal(10,2);not null" json:"subtotal"` + ShippingFee float64 `gorm:"type:decimal(10,2);default:0" json:"shipping_fee"` + ServiceFee float64 `gorm:"type:decimal(10,2);default:0" json:"service_fee"` + Tax float64 `gorm:"type:decimal(10,2);default:0" json:"tax"` + TotalAmount float64 `gorm:"type:decimal(10,2);not null" json:"total_amount"` + RefundAmount *float64 `gorm:"type:decimal(10,2)" json:"refund_amount"` + RefundStatus string `gorm:"size:20" json:"refund_status"` + RefundReason string `json:"refund_reason"` + Status string `gorm:"size:20;not null;default:'pending_payment'" json:"status"` + ShippingAddressID *uint `json:"shipping_address_id"` + TrackingNumber string `gorm:"size:100" json:"tracking_number"` + ShippingPhoto string `gorm:"type:text" json:"shipping_photo"` + ExpressPhoto string `gorm:"type:text" json:"express_photo"` + CustomsPhoto string `gorm:"type:text" json:"customs_photo"` + PaymentMethod string `gorm:"size:50" json:"payment_method"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + OrderItems []OrderItem `json:"order_items,omitempty"` + ShippingAddress *Address `json:"shipping_address,omitempty"` + User User `json:"user,omitempty"` +} + +func (Order) TableName() string { + return "orders" +} + +type OrderItem struct { + ID uint `gorm:"primaryKey" json:"id"` + OrderID uint `gorm:"index;not null" json:"order_id"` + ProductID uint `gorm:"index;not null" json:"product_id"` + Quantity int `gorm:"not null" json:"quantity"` + Price float64 `gorm:"type:decimal(10,2);not null" json:"price"` + Product Product `json:"product,omitempty"` +} + +func (OrderItem) TableName() string { + return "order_items" +} + +const ( + OrderStatusPendingPayment = "pending_payment" + OrderStatusPendingConfirm = "pending_confirm" + OrderStatusPendingShip = "pending_ship" + OrderStatusShipped = "shipped" + OrderStatusCompleted = "completed" + OrderStatusRefunding = "refunding" + OrderStatusRefunded = "refunded" + OrderStatusCancelled = "cancelled" +) + +const ( + RefundStatusNone = "" + RefundStatusPending = "pending" + RefundStatusApproved = "approved" + RefundStatusRejected = "rejected" + RefundStatusCompleted = "completed" +) diff --git a/backend/internal/models/product.go b/backend/internal/models/product.go new file mode 100644 index 0000000..16c8c35 --- /dev/null +++ b/backend/internal/models/product.go @@ -0,0 +1,48 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type Product struct { + ID uint `gorm:"primaryKey" json:"id"` + Name string `gorm:"size:255;not null" json:"name"` + Description string `json:"description"` + Price float64 `gorm:"type:decimal(10,2);not null" json:"price"` + MinPurchase int `gorm:"default:1" json:"min_purchase"` + MaxPurchase *int `json:"max_purchase"` + MinWeight *float64 `json:"min_weight"` + MaxWeight *float64 `json:"max_weight"` + MinAmount *float64 `json:"min_amount"` + MaxAmount *float64 `json:"max_amount"` + RequireCredit bool `gorm:"default:false" json:"require_credit"` + CreditCost int `gorm:"default:0" json:"credit_cost"` + CreditReward int `gorm:"default:0" json:"credit_reward"` + Images string `gorm:"type:text" json:"images"` + IsActive bool `gorm:"default:true" json:"is_active"` + BrandID *uint `json:"brand_id"` + Brand *Brand `json:"brand,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + Categories []Category `gorm:"many2many:product_categories;" json:"categories,omitempty"` + CustomFields []ProductCustomField `json:"custom_fields,omitempty"` +} + +func (Product) TableName() string { + return "products" +} + +type ProductCustomField struct { + ID uint `gorm:"primaryKey" json:"id"` + ProductID uint `gorm:"index;not null" json:"product_id"` + FieldName string `gorm:"size:100;not null" json:"field_name"` + FieldValue string `json:"field_value"` + CreatedAt time.Time `json:"created_at"` +} + +func (ProductCustomField) TableName() string { + return "product_custom_fields" +} diff --git a/backend/internal/models/system.go b/backend/internal/models/system.go new file mode 100644 index 0000000..37d1a9d --- /dev/null +++ b/backend/internal/models/system.go @@ -0,0 +1,32 @@ +package models + +import ( + "time" +) + +type SystemSetting struct { + ID uint `gorm:"primaryKey" json:"id"` + Key string `gorm:"uniqueIndex;size:100;not null" json:"key"` + Value string `gorm:"type:text" json:"value"` + UpdatedAt time.Time `json:"updated_at"` +} + +func (SystemSetting) TableName() string { + return "system_settings" +} + +type SupplierAuthorization struct { + ID uint `gorm:"primaryKey" json:"id"` + SupplierID uint `gorm:"index;not null" json:"supplier_id"` + Type string `gorm:"size:20;not null" json:"type"` + RefID uint `gorm:"not null" json:"ref_id"` +} + +func (SupplierAuthorization) TableName() string { + return "supplier_authorizations" +} + +const ( + AuthTypeCategory = "category" + AuthTypeProduct = "product" +) diff --git a/backend/internal/models/ticket.go b/backend/internal/models/ticket.go new file mode 100644 index 0000000..9c793cb --- /dev/null +++ b/backend/internal/models/ticket.go @@ -0,0 +1,39 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type Ticket struct { + ID uint `gorm:"primaryKey" json:"id"` + UserID uint `gorm:"index;not null" json:"user_id"` + Title string `gorm:"size:200;not null" json:"title"` + Content string `gorm:"type:text;not null" json:"content"` + Category string `gorm:"size:50" json:"category"` + Status string `gorm:"size:20;not null;default:'pending'" json:"status"` + AssignedTo *uint `json:"assigned_to"` + Reply string `gorm:"type:text" json:"reply"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + User User `gorm:"foreignKey:UserID" json:"user,omitempty"` + Assignee *User `gorm:"foreignKey:AssignedTo" json:"assignee,omitempty"` +} + +func (Ticket) TableName() string { + return "tickets" +} + +const ( + TicketStatusPending = "pending" + TicketStatusProcessing = "processing" + TicketStatusResolved = "resolved" + TicketStatusClosed = "closed" + + TicketCategoryOrder = "order" + TicketCategoryAccount = "account" + TicketCategoryProduct = "product" + TicketCategoryOther = "other" +) diff --git a/backend/internal/models/user.go b/backend/internal/models/user.go new file mode 100644 index 0000000..f2aa790 --- /dev/null +++ b/backend/internal/models/user.go @@ -0,0 +1,27 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +type User struct { + ID uint `gorm:"primaryKey" json:"id"` + Username string `gorm:"uniqueIndex;size:50;not null" json:"username"` + Email string `gorm:"uniqueIndex;size:100;not null" json:"email"` + PasswordHash string `gorm:"size:255;not null" json:"-"` + Role string `gorm:"size:20;not null;default:'user'" json:"role"` + PurchaseCredits int `gorm:"default:0" json:"purchase_credits"` + InviteCode string `gorm:"uniqueIndex;size:20" json:"invite_code"` + ReferredBy *uint `json:"referred_by"` + EmailVerified bool `gorm:"default:false" json:"email_verified"` + IsActive bool `gorm:"default:true" json:"is_active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` +} + +func (User) TableName() string { + return "users" +} diff --git a/backend/internal/schemas/address.go b/backend/internal/schemas/address.go new file mode 100644 index 0000000..01af3ca --- /dev/null +++ b/backend/internal/schemas/address.go @@ -0,0 +1,21 @@ +package schemas + +type CreateAddressRequest struct { + Name string `json:"name" binding:"required"` + Phone string `json:"phone" binding:"required"` + Province string `json:"province"` + City string `json:"city"` + District string `json:"district"` + Address string `json:"address" binding:"required"` + IsDefault bool `json:"is_default"` +} + +type UpdateAddressRequest struct { + Name *string `json:"name"` + Phone *string `json:"phone"` + Province *string `json:"province"` + City *string `json:"city"` + District *string `json:"district"` + Address *string `json:"address"` + IsDefault *bool `json:"is_default"` +} diff --git a/backend/internal/schemas/article.go b/backend/internal/schemas/article.go new file mode 100644 index 0000000..722faaf --- /dev/null +++ b/backend/internal/schemas/article.go @@ -0,0 +1,21 @@ +package schemas + +type CreateArticleRequest struct { + Title string `json:"title" binding:"required"` + Content string `json:"content" binding:"required"` + Summary string `json:"summary"` + CoverImage string `json:"cover_image"` + IsPinned bool `json:"is_pinned"` + IsPublished bool `json:"is_published"` + SortOrder int `json:"sort_order"` +} + +type UpdateArticleRequest struct { + Title *string `json:"title"` + Content *string `json:"content"` + Summary *string `json:"summary"` + CoverImage *string `json:"cover_image"` + IsPinned *bool `json:"is_pinned"` + IsPublished *bool `json:"is_published"` + SortOrder *int `json:"sort_order"` +} diff --git a/backend/internal/schemas/auth.go b/backend/internal/schemas/auth.go new file mode 100644 index 0000000..bf1aaf2 --- /dev/null +++ b/backend/internal/schemas/auth.go @@ -0,0 +1,54 @@ +package schemas + +type RegisterRequest struct { + Email string `json:"email" binding:"required,email"` + Username string `json:"username" binding:"required,min=2,max=50"` + Password string `json:"password" binding:"required,min=6"` + InviteCode string `json:"invite_code"` +} + +type LoginRequest struct { + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required"` +} + +type ForgotPasswordRequest struct { + Email string `json:"email" binding:"required,email"` +} + +type ResetPasswordRequest struct { + Email string `json:"email" binding:"required,email"` + Code string `json:"code" binding:"required"` + Password string `json:"password" binding:"required,min=6"` +} + +type VerifyEmailRequest struct { + Email string `json:"email" binding:"required,email"` + Code string `json:"code" binding:"required"` +} + +type ChangePasswordRequest struct { + OldPassword string `json:"old_password" binding:"required"` + NewPassword string `json:"new_password" binding:"required,min=6"` +} + +type AuthResponse struct { + Token string `json:"token"` + User UserResponse `json:"user"` +} + +type UserResponse struct { + ID uint `json:"id"` + Username string `json:"username"` + Email string `json:"email"` + Role string `json:"role"` + PurchaseCredits int `json:"purchase_credits"` + InviteCode string `json:"invite_code"` + EmailVerified bool `json:"email_verified"` +} + +type InstallRequest struct { + Username string `json:"username" binding:"required,min=2,max=50"` + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required,min=6"` +} diff --git a/backend/internal/schemas/lottery.go b/backend/internal/schemas/lottery.go new file mode 100644 index 0000000..12d8795 --- /dev/null +++ b/backend/internal/schemas/lottery.go @@ -0,0 +1,37 @@ +package schemas + +type CreateLotteryRequest struct { + Name string `json:"name" binding:"required"` + Description string `json:"description"` + StartTime string `json:"start_time" binding:"required"` + EndTime string `json:"end_time" binding:"required"` + Cycle string `json:"cycle"` + DailyQuota *int `json:"daily_quota"` + TotalQuota *int `json:"total_quota"` + RegistrationValidity *int `json:"registration_validity"` +} + +type UpdateLotteryRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + StartTime *string `json:"start_time"` + EndTime *string `json:"end_time"` + Cycle *string `json:"cycle"` + DailyQuota *int `json:"daily_quota"` + TotalQuota *int `json:"total_quota"` + RegistrationValidity *int `json:"registration_validity"` + IsActive *bool `json:"is_active"` +} + +type AddLotteryPrizeRequest struct { + Name string `json:"name" binding:"required"` + Type string `json:"type" binding:"required,oneof=physical virtual credit"` + Quantity int `json:"quantity" binding:"required,min=1"` + Weight int `json:"weight"` + CreditReward *int `json:"credit_reward"` + DrawMode string `json:"draw_mode"` +} + +type RegisterLotteryRequest struct { + LotteryID uint `json:"lottery_id" binding:"required"` +} diff --git a/backend/internal/schemas/order.go b/backend/internal/schemas/order.go new file mode 100644 index 0000000..7b3f5cd --- /dev/null +++ b/backend/internal/schemas/order.go @@ -0,0 +1,30 @@ +package schemas + +type AddToCartRequest struct { + ProductID uint `json:"product_id" binding:"required"` + Quantity int `json:"quantity" binding:"required,min=1"` +} + +type UpdateCartRequest struct { + Quantity int `json:"quantity" binding:"required,min=1"` +} + +type CreateOrderRequest struct { + ShippingAddressID uint `json:"shipping_address_id" binding:"required"` + PaymentMethod string `json:"payment_method"` +} + +type RefundRequest struct { + Reason string `json:"reason" binding:"required"` +} + +type ShipOrderRequest struct { + TrackingNumber string `json:"tracking_number" binding:"required"` + ShippingPhoto string `json:"shipping_photo"` + ExpressPhoto string `json:"express_photo"` + CustomsPhoto string `json:"customs_photo"` +} + +type ProcessRefundRequest struct { + Status string `json:"status" binding:"required,oneof=approved rejected"` +} diff --git a/backend/internal/schemas/product.go b/backend/internal/schemas/product.go new file mode 100644 index 0000000..cc92b5d --- /dev/null +++ b/backend/internal/schemas/product.go @@ -0,0 +1,85 @@ +package schemas + +type CreateCategoryRequest struct { + Name string `json:"name" binding:"required"` + Description string `json:"description"` + ParentID *uint `json:"parent_id"` + MinAmount *float64 `json:"min_amount"` + MaxAmount *float64 `json:"max_amount"` + MinQuantity *int `json:"min_quantity"` + MaxQuantity *int `json:"max_quantity"` + MinWeight *float64 `json:"min_weight"` + MaxWeight *float64 `json:"max_weight"` + SortOrder int `json:"sort_order"` +} + +type UpdateCategoryRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + ParentID *uint `json:"parent_id"` + MinAmount *float64 `json:"min_amount"` + MaxAmount *float64 `json:"max_amount"` + MinQuantity *int `json:"min_quantity"` + MaxQuantity *int `json:"max_quantity"` + MinWeight *float64 `json:"min_weight"` + MaxWeight *float64 `json:"max_weight"` + SortOrder *int `json:"sort_order"` +} + +type CreateProductRequest struct { + Name string `json:"name" binding:"required"` + Description string `json:"description"` + Price float64 `json:"price" binding:"required"` + MinPurchase int `json:"min_purchase"` + MaxPurchase *int `json:"max_purchase"` + MinWeight *float64 `json:"min_weight"` + MaxWeight *float64 `json:"max_weight"` + MinAmount *float64 `json:"min_amount"` + MaxAmount *float64 `json:"max_amount"` + RequireCredit bool `json:"require_credit"` + CreditCost int `json:"credit_cost"` + CreditReward int `json:"credit_reward"` + Images string `json:"images"` + BrandID *uint `json:"brand_id"` + CategoryIDs []uint `json:"category_ids"` + CustomFields []CustomFieldRequest `json:"custom_fields"` +} + +type UpdateProductRequest struct { + Name *string `json:"name"` + Description *string `json:"description"` + Price *float64 `json:"price"` + MinPurchase *int `json:"min_purchase"` + MaxPurchase *int `json:"max_purchase"` + MinWeight *float64 `json:"min_weight"` + MaxWeight *float64 `json:"max_weight"` + MinAmount *float64 `json:"min_amount"` + MaxAmount *float64 `json:"max_amount"` + RequireCredit *bool `json:"require_credit"` + CreditCost *int `json:"credit_cost"` + CreditReward *int `json:"credit_reward"` + Images *string `json:"images"` + IsActive *bool `json:"is_active"` + BrandID *uint `json:"brand_id"` + CategoryIDs []uint `json:"category_ids"` + CustomFields []CustomFieldRequest `json:"custom_fields"` +} + +type CustomFieldRequest struct { + FieldName string `json:"field_name" binding:"required"` + FieldValue string `json:"field_value"` +} + +type PaginationRequest struct { + Page int `form:"page,default=1"` + PageSize int `form:"page_size,default=20"` +} + +type ProductListRequest struct { + PaginationRequest + CategoryID *uint `form:"category_id"` + BrandID *uint `form:"brand_id"` + Keyword string `form:"keyword"` + MinPrice *float64 `form:"min_price"` + MaxPrice *float64 `form:"max_price"` +} diff --git a/backend/internal/schemas/system.go b/backend/internal/schemas/system.go new file mode 100644 index 0000000..8bef1d4 --- /dev/null +++ b/backend/internal/schemas/system.go @@ -0,0 +1,20 @@ +package schemas + +type UpdateSettingsRequest struct { + Settings map[string]string `json:"settings" binding:"required"` +} + +type AuthorizeSupplierRequest struct { + Type string `json:"type" binding:"required,oneof=category product"` + RefID uint `json:"ref_id" binding:"required"` +} + +type AddSupplierRequest struct { + Username string `json:"username" binding:"required"` + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required,min=6"` +} + +type UpdateInventoryRequest struct { + Quantity int `json:"quantity" binding:"required,min=0"` +} diff --git a/backend/internal/schemas/ticket.go b/backend/internal/schemas/ticket.go new file mode 100644 index 0000000..3bd38ed --- /dev/null +++ b/backend/internal/schemas/ticket.go @@ -0,0 +1,13 @@ +package schemas + +type CreateTicketRequest struct { + Title string `json:"title" binding:"required"` + Content string `json:"content" binding:"required"` + Category string `json:"category"` +} + +type UpdateTicketRequest struct { + Status *string `json:"status"` + AssignedTo *uint `json:"assigned_to"` + Reply *string `json:"reply"` +} diff --git a/backend/internal/utils/database.go b/backend/internal/utils/database.go new file mode 100644 index 0000000..dbb73a5 --- /dev/null +++ b/backend/internal/utils/database.go @@ -0,0 +1,79 @@ +package utils + +import ( + "fmt" + "log" + + "sale/internal/config" + "sale/internal/models" + + sqlite "github.com/glebarez/sqlite" + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var DB *gorm.DB + +func InitDB() { + var err error + cfg := config.AppConfig.Database + + switch cfg.Driver { + case "mysql": + dsn := fmt.Sprintf( + "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local", + cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DBName, + ) + DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) + case "postgres": + dsn := fmt.Sprintf( + "host=%s port=%s user=%s password=%s dbname=%s sslmode=%s", + cfg.Host, cfg.Port, cfg.User, cfg.Password, cfg.DBName, cfg.SSLMode, + ) + DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) + default: + DB, err = gorm.Open(sqlite.Open(cfg.FilePath), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), + }) + } + + if err != nil { + log.Fatalf("Failed to connect to database: %v", err) + } + + log.Printf("Database connected successfully (driver: %s)", cfg.Driver) +} + +func AutoMigrate() { + err := DB.AutoMigrate( + &models.User{}, + &models.Category{}, + &models.Brand{}, + &models.Product{}, + &models.ProductCustomField{}, + &models.Inventory{}, + &models.Cart{}, + &models.Order{}, + &models.OrderItem{}, + &models.Address{}, + &models.Lottery{}, + &models.LotteryPrize{}, + &models.LotteryParticipant{}, + &models.LotteryWinner{}, + &models.Ticket{}, + &models.SystemSetting{}, + &models.SupplierAuthorization{}, + &models.Article{}, + ) + if err != nil { + log.Fatalf("Failed to migrate database: %v", err) + } + + log.Println("Database migrated successfully") +} diff --git a/backend/internal/utils/email.go b/backend/internal/utils/email.go new file mode 100644 index 0000000..2bb6d80 --- /dev/null +++ b/backend/internal/utils/email.go @@ -0,0 +1,50 @@ +package utils + +import ( + "fmt" + "net/smtp" + + "sale/internal/config" +) + +func SendEmail(to, subject, body string) error { + cfg := config.AppConfig.SMTP + if cfg.Host == "" { + return fmt.Errorf("SMTP not configured") + } + + auth := smtp.PlainAuth("", cfg.User, cfg.Password, cfg.Host) + + msg := fmt.Sprintf( + "From: %s\r\nTo: %s\r\nSubject: %s\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n%s", + cfg.From, to, subject, body, + ) + + return smtp.SendMail( + fmt.Sprintf("%s:%s", cfg.Host, cfg.Port), + auth, + cfg.From, + []string{to}, + []byte(msg), + ) +} + +func SendVerifyEmail(to, code string) error { + subject := "邮箱验证码" + body := fmt.Sprintf(` +

邮箱验证

+

您的验证码是:%s

+

验证码有效期为30分钟,请尽快使用。

+ `, code) + return SendEmail(to, subject, body) +} + +func SendResetPasswordEmail(to, code string) error { + subject := "重置密码验证码" + body := fmt.Sprintf(` +

重置密码

+

您的验证码是:%s

+

验证码有效期为30分钟,请尽快使用。

+ `, code) + return SendEmail(to, subject, body) +} diff --git a/backend/internal/utils/jwt.go b/backend/internal/utils/jwt.go new file mode 100644 index 0000000..a3b8fa7 --- /dev/null +++ b/backend/internal/utils/jwt.go @@ -0,0 +1,46 @@ +package utils + +import ( + "errors" + "time" + + "sale/internal/config" + + "github.com/golang-jwt/jwt/v5" +) + +type Claims struct { + UserID uint `json:"user_id"` + Role string `json:"role"` + jwt.RegisteredClaims +} + +func GenerateToken(userID uint, role string) (string, error) { + claims := Claims{ + UserID: userID, + Role: role, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(config.AppConfig.JWT.ExpireHour) * time.Hour)), + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString([]byte(config.AppConfig.JWT.Secret)) +} + +func ParseToken(tokenString string) (*Claims, error) { + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + return []byte(config.AppConfig.JWT.Secret), nil + }) + + if err != nil { + return nil, err + } + + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + return claims, nil + } + + return nil, errors.New("invalid token") +} diff --git a/backend/internal/utils/password.go b/backend/internal/utils/password.go new file mode 100644 index 0000000..6485cfb --- /dev/null +++ b/backend/internal/utils/password.go @@ -0,0 +1,15 @@ +package utils + +import ( + "golang.org/x/crypto/bcrypt" +) + +func HashPassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(bytes), err +} + +func CheckPassword(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} diff --git a/backend/internal/utils/random.go b/backend/internal/utils/random.go new file mode 100644 index 0000000..32e8839 --- /dev/null +++ b/backend/internal/utils/random.go @@ -0,0 +1,26 @@ +package utils + +import ( + "crypto/rand" + "math/big" +) + +func GenerateInviteCode() string { + const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + code := make([]byte, 8) + for i := range code { + n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) + code[i] = charset[n.Int64()] + } + return string(code) +} + +func GenerateVerifyCode() string { + const charset = "0123456789" + code := make([]byte, 6) + for i := range code { + n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) + code[i] = charset[n.Int64()] + } + return string(code) +} diff --git a/backend/scripts/insert_fake_data.py b/backend/scripts/insert_fake_data.py new file mode 100644 index 0000000..4a210cd --- /dev/null +++ b/backend/scripts/insert_fake_data.py @@ -0,0 +1,63 @@ +import sqlite3 +from datetime import datetime, timedelta +import random + +conn = sqlite3.connect('E:/Code/sale/backend/sale.db') +cursor = conn.cursor() + +articles = [ + ('新品上市:2024春季限定系列发布', '我们很高兴地宣布2024春季限定系列正式上线,包含多款精选商品,欢迎选购。', 'https://picsum.photos/seed/art1/800/400'), + ('购物指南:如何选择适合自己的商品', '本文将为您详细介绍如何根据个人需求选择最合适的商品,帮助您做出明智的购买决策。', 'https://picsum.photos/seed/art2/800/400'), + ('会员福利升级通知', '尊敬的用户,我们已全面升级会员福利体系,现在注册即可享受更多专属优惠。', 'https://picsum.photos/seed/art3/800/400'), + ('物流配送说明', '为了提供更好的购物体验,我们优化了物流配送流程,现在下单可享受更快的配送服务。', 'https://picsum.photos/seed/art4/800/400'), + ('售后服务政策更新', '我们更新了售后服务政策,现在支持7天无理由退换货,让您购物更放心。', 'https://picsum.photos/seed/art5/800/400'), + ('限时优惠活动预告', '本周五将开启限时优惠活动,多款热门商品参与折扣,敬请期待。', 'https://picsum.photos/seed/art6/800/400'), + ('品牌故事:我们的初心', '了解我们的品牌故事,感受我们对品质的追求和对客户的承诺。', 'https://picsum.photos/seed/art7/800/400'), + ('用户评价精选:听听他们怎么说', '我们收集了用户的真实评价,看看大家对我们商品和服务的反馈。', 'https://picsum.photos/seed/art8/800/400'), + ('安全购物须知', '网络购物安全指南,教您如何保护个人信息,安全购物。', 'https://picsum.photos/seed/art9/800/400'), + ('节日特惠活动即将开始', '节日特惠活动即将开启,超多优惠等你来抢,不要错过!', 'https://picsum.photos/seed/art10/800/400'), +] + +lotteries = [ + ('新年大抽奖', '参与新年抽奖,赢取丰厚奖品', '2024-01-01 00:00:00', '2024-12-31 23:59:59'), + ('春季幸运抽奖', '春季限定抽奖活动,好礼送不停', '2024-03-01 00:00:00', '2024-05-31 23:59:59'), + ('会员专属抽奖', '会员专属抽奖活动,更多惊喜等你来', '2024-04-01 00:00:00', '2024-06-30 23:59:59'), +] + +prizes = [ + ('一等奖:iPhone 15 Pro', 'product', 1, 1, None), + ('二等奖:AirPods Pro', 'product', 3, 2, None), + ('三等奖:100积分', 'credits', 10, 3, 100), + ('参与奖:10积分', 'credits', 50, 1, 10), +] + +print('插入资讯数据...') +for i, (title, summary, cover) in enumerate(articles): + content = f'

{title}

{summary}

这是文章的详细内容,包含了更多相关信息和说明。感谢您的阅读!

' + created_at = datetime.now() - timedelta(days=random.randint(1, 30)) + is_pinned = 1 if i < 2 else 0 + cursor.execute(''' + INSERT INTO articles (title, summary, content, cover_image, is_pinned, author_id, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, 1, ?, ?) + ''', (title, summary, content, cover, is_pinned, created_at.strftime('%Y-%m-%d %H:%M:%S'), created_at.strftime('%Y-%m-%d %H:%M:%S'))) +print(f'已插入 {len(articles)} 条资讯') + +print('插入抽奖数据...') +for name, description, start_time, end_time in lotteries: + cursor.execute(''' + INSERT INTO lotteries (name, description, start_time, end_time, created_at, updated_at) + VALUES (?, ?, ?, ?, datetime('now'), datetime('now')) + ''', (name, description, start_time, end_time)) + lottery_id = cursor.lastrowid + + for prize_name, prize_type, quantity, weight, credit_reward in prizes: + cursor.execute(''' + INSERT INTO lottery_prizes (lottery_id, name, type, quantity, weight, credit_reward) + VALUES (?, ?, ?, ?, ?, ?) + ''', (lottery_id, prize_name, prize_type, quantity, weight, credit_reward)) +print(f'已插入 {len(lotteries)} 个抽奖活动') + +conn.commit() +cursor.close() +conn.close() +print('完成!') diff --git a/backend/uploads/images/000af923-a143-439d-afb2-2f30ccaec948.webp b/backend/uploads/images/000af923-a143-439d-afb2-2f30ccaec948.webp new file mode 100644 index 0000000..3ca4e88 Binary files /dev/null and b/backend/uploads/images/000af923-a143-439d-afb2-2f30ccaec948.webp differ diff --git a/backend/uploads/images/00318bb7-9005-4cbf-ae60-177c3ddaf36e.webp b/backend/uploads/images/00318bb7-9005-4cbf-ae60-177c3ddaf36e.webp new file mode 100644 index 0000000..f9f110a Binary files /dev/null and b/backend/uploads/images/00318bb7-9005-4cbf-ae60-177c3ddaf36e.webp differ diff --git a/backend/uploads/images/0073f8b1-5c88-4991-9e42-5eeaa0ec804a.webp b/backend/uploads/images/0073f8b1-5c88-4991-9e42-5eeaa0ec804a.webp new file mode 100644 index 0000000..d02cbea Binary files /dev/null and b/backend/uploads/images/0073f8b1-5c88-4991-9e42-5eeaa0ec804a.webp differ diff --git a/backend/uploads/images/00b1e006-0850-462f-8372-94825d62fc49.webp b/backend/uploads/images/00b1e006-0850-462f-8372-94825d62fc49.webp new file mode 100644 index 0000000..4abb153 Binary files /dev/null and b/backend/uploads/images/00b1e006-0850-462f-8372-94825d62fc49.webp differ diff --git a/backend/uploads/images/00f8696d-9a6d-4137-b123-fbdd8fca831c.webp b/backend/uploads/images/00f8696d-9a6d-4137-b123-fbdd8fca831c.webp new file mode 100644 index 0000000..589d0e0 Binary files /dev/null and b/backend/uploads/images/00f8696d-9a6d-4137-b123-fbdd8fca831c.webp differ diff --git a/backend/uploads/images/016d5634-db70-46b3-aba6-3ae36a3c31b1.webp b/backend/uploads/images/016d5634-db70-46b3-aba6-3ae36a3c31b1.webp new file mode 100644 index 0000000..f134630 Binary files /dev/null and b/backend/uploads/images/016d5634-db70-46b3-aba6-3ae36a3c31b1.webp differ diff --git a/backend/uploads/images/01b7ce2f-81fa-4207-b4d8-7135b07b8d25.webp b/backend/uploads/images/01b7ce2f-81fa-4207-b4d8-7135b07b8d25.webp new file mode 100644 index 0000000..e353586 Binary files /dev/null and b/backend/uploads/images/01b7ce2f-81fa-4207-b4d8-7135b07b8d25.webp differ diff --git a/backend/uploads/images/01d3b90a-61f3-420c-8274-f54275345f7b.webp b/backend/uploads/images/01d3b90a-61f3-420c-8274-f54275345f7b.webp new file mode 100644 index 0000000..115eda9 Binary files /dev/null and b/backend/uploads/images/01d3b90a-61f3-420c-8274-f54275345f7b.webp differ diff --git a/backend/uploads/images/022e20f1-a2ab-48fb-96dd-8120e03146ad.webp b/backend/uploads/images/022e20f1-a2ab-48fb-96dd-8120e03146ad.webp new file mode 100644 index 0000000..35b6d56 Binary files /dev/null and b/backend/uploads/images/022e20f1-a2ab-48fb-96dd-8120e03146ad.webp differ diff --git a/backend/uploads/images/0318c4e0-164d-44f6-9f46-dd23a47220e1.webp b/backend/uploads/images/0318c4e0-164d-44f6-9f46-dd23a47220e1.webp new file mode 100644 index 0000000..3c42fcb Binary files /dev/null and b/backend/uploads/images/0318c4e0-164d-44f6-9f46-dd23a47220e1.webp differ diff --git a/backend/uploads/images/03d69d30-c37f-48ba-acf2-c371dd2ab652.webp b/backend/uploads/images/03d69d30-c37f-48ba-acf2-c371dd2ab652.webp new file mode 100644 index 0000000..c4b6dab Binary files /dev/null and b/backend/uploads/images/03d69d30-c37f-48ba-acf2-c371dd2ab652.webp differ diff --git a/backend/uploads/images/0408d44e-e297-43ed-a0ae-1c7612096cb1.webp b/backend/uploads/images/0408d44e-e297-43ed-a0ae-1c7612096cb1.webp new file mode 100644 index 0000000..f40c3d3 Binary files /dev/null and b/backend/uploads/images/0408d44e-e297-43ed-a0ae-1c7612096cb1.webp differ diff --git a/backend/uploads/images/04226cc8-8e9e-4044-8ee1-835263dbb76f.webp b/backend/uploads/images/04226cc8-8e9e-4044-8ee1-835263dbb76f.webp new file mode 100644 index 0000000..3e13c2a Binary files /dev/null and b/backend/uploads/images/04226cc8-8e9e-4044-8ee1-835263dbb76f.webp differ diff --git a/backend/uploads/images/044297d8-f622-48d2-8333-b5026a5b2eed.webp b/backend/uploads/images/044297d8-f622-48d2-8333-b5026a5b2eed.webp new file mode 100644 index 0000000..ffc8f39 Binary files /dev/null and b/backend/uploads/images/044297d8-f622-48d2-8333-b5026a5b2eed.webp differ diff --git a/backend/uploads/images/05015442-280c-4f62-8344-76ea12276e85.webp b/backend/uploads/images/05015442-280c-4f62-8344-76ea12276e85.webp new file mode 100644 index 0000000..09b0b5a Binary files /dev/null and b/backend/uploads/images/05015442-280c-4f62-8344-76ea12276e85.webp differ diff --git a/backend/uploads/images/057ee786-8637-4e9e-a0c3-39a77def8132.webp b/backend/uploads/images/057ee786-8637-4e9e-a0c3-39a77def8132.webp new file mode 100644 index 0000000..90e1ee6 Binary files /dev/null and b/backend/uploads/images/057ee786-8637-4e9e-a0c3-39a77def8132.webp differ diff --git a/backend/uploads/images/065bd2bc-74a6-4636-9dd4-63a2556ef025.webp b/backend/uploads/images/065bd2bc-74a6-4636-9dd4-63a2556ef025.webp new file mode 100644 index 0000000..fb5be21 Binary files /dev/null and b/backend/uploads/images/065bd2bc-74a6-4636-9dd4-63a2556ef025.webp differ diff --git a/backend/uploads/images/06e7ceab-b8ea-4657-9e98-1b27b8cc9a3b.webp b/backend/uploads/images/06e7ceab-b8ea-4657-9e98-1b27b8cc9a3b.webp new file mode 100644 index 0000000..2df62f9 Binary files /dev/null and b/backend/uploads/images/06e7ceab-b8ea-4657-9e98-1b27b8cc9a3b.webp differ diff --git a/backend/uploads/images/06f048ff-5780-4251-83a4-24f6c4c26d39.webp b/backend/uploads/images/06f048ff-5780-4251-83a4-24f6c4c26d39.webp new file mode 100644 index 0000000..06f8443 Binary files /dev/null and b/backend/uploads/images/06f048ff-5780-4251-83a4-24f6c4c26d39.webp differ diff --git a/backend/uploads/images/0702597f-9089-4334-a286-05b16ab03057.webp b/backend/uploads/images/0702597f-9089-4334-a286-05b16ab03057.webp new file mode 100644 index 0000000..2a63041 Binary files /dev/null and b/backend/uploads/images/0702597f-9089-4334-a286-05b16ab03057.webp differ diff --git a/backend/uploads/images/07e3bf6b-755f-49c8-ba13-bde16a279ef6.webp b/backend/uploads/images/07e3bf6b-755f-49c8-ba13-bde16a279ef6.webp new file mode 100644 index 0000000..99a2fd9 Binary files /dev/null and b/backend/uploads/images/07e3bf6b-755f-49c8-ba13-bde16a279ef6.webp differ diff --git a/backend/uploads/images/080f8053-f560-4e9e-b357-ae75bc1bdb39.webp b/backend/uploads/images/080f8053-f560-4e9e-b357-ae75bc1bdb39.webp new file mode 100644 index 0000000..a9e0c95 Binary files /dev/null and b/backend/uploads/images/080f8053-f560-4e9e-b357-ae75bc1bdb39.webp differ diff --git a/backend/uploads/images/08aeee74-c930-41c2-9337-9cd3f97f195d.webp b/backend/uploads/images/08aeee74-c930-41c2-9337-9cd3f97f195d.webp new file mode 100644 index 0000000..3faaefb Binary files /dev/null and b/backend/uploads/images/08aeee74-c930-41c2-9337-9cd3f97f195d.webp differ diff --git a/backend/uploads/images/09043140-ea9a-4436-8258-6b02e9527e78.webp b/backend/uploads/images/09043140-ea9a-4436-8258-6b02e9527e78.webp new file mode 100644 index 0000000..825cdf6 Binary files /dev/null and b/backend/uploads/images/09043140-ea9a-4436-8258-6b02e9527e78.webp differ diff --git a/backend/uploads/images/0954cb3f-b046-4a86-9b4a-0e538f06b848.webp b/backend/uploads/images/0954cb3f-b046-4a86-9b4a-0e538f06b848.webp new file mode 100644 index 0000000..66edc22 Binary files /dev/null and b/backend/uploads/images/0954cb3f-b046-4a86-9b4a-0e538f06b848.webp differ diff --git a/backend/uploads/images/097a5632-d7b3-4f94-93a2-f70e024e6874.webp b/backend/uploads/images/097a5632-d7b3-4f94-93a2-f70e024e6874.webp new file mode 100644 index 0000000..371781f Binary files /dev/null and b/backend/uploads/images/097a5632-d7b3-4f94-93a2-f70e024e6874.webp differ diff --git a/backend/uploads/images/0a2b3758-7f89-4372-8fbd-6a7ad4eb24e1.webp b/backend/uploads/images/0a2b3758-7f89-4372-8fbd-6a7ad4eb24e1.webp new file mode 100644 index 0000000..3d62f71 Binary files /dev/null and b/backend/uploads/images/0a2b3758-7f89-4372-8fbd-6a7ad4eb24e1.webp differ diff --git a/backend/uploads/images/0a6b769c-f6b2-4302-8c1f-e0e8f92acc6b.webp b/backend/uploads/images/0a6b769c-f6b2-4302-8c1f-e0e8f92acc6b.webp new file mode 100644 index 0000000..d00a652 Binary files /dev/null and b/backend/uploads/images/0a6b769c-f6b2-4302-8c1f-e0e8f92acc6b.webp differ diff --git a/backend/uploads/images/0afc416a-4ceb-462e-b667-cd6625003f69.webp b/backend/uploads/images/0afc416a-4ceb-462e-b667-cd6625003f69.webp new file mode 100644 index 0000000..3c504e6 Binary files /dev/null and b/backend/uploads/images/0afc416a-4ceb-462e-b667-cd6625003f69.webp differ diff --git a/backend/uploads/images/0c7cc98f-1897-4233-b111-d2690d8e1ce1.webp b/backend/uploads/images/0c7cc98f-1897-4233-b111-d2690d8e1ce1.webp new file mode 100644 index 0000000..c133af9 Binary files /dev/null and b/backend/uploads/images/0c7cc98f-1897-4233-b111-d2690d8e1ce1.webp differ diff --git a/backend/uploads/images/0d1dc0a5-75ad-4b06-af3c-1a8cae5b7301.webp b/backend/uploads/images/0d1dc0a5-75ad-4b06-af3c-1a8cae5b7301.webp new file mode 100644 index 0000000..213eb84 Binary files /dev/null and b/backend/uploads/images/0d1dc0a5-75ad-4b06-af3c-1a8cae5b7301.webp differ diff --git a/backend/uploads/images/0d52dbcf-c548-4d7c-9c0f-88c4be2853b7.webp b/backend/uploads/images/0d52dbcf-c548-4d7c-9c0f-88c4be2853b7.webp new file mode 100644 index 0000000..257c2e4 Binary files /dev/null and b/backend/uploads/images/0d52dbcf-c548-4d7c-9c0f-88c4be2853b7.webp differ diff --git a/backend/uploads/images/0d56e14b-9676-49ed-93e0-58503cdab39d.webp b/backend/uploads/images/0d56e14b-9676-49ed-93e0-58503cdab39d.webp new file mode 100644 index 0000000..5481f5b Binary files /dev/null and b/backend/uploads/images/0d56e14b-9676-49ed-93e0-58503cdab39d.webp differ diff --git a/backend/uploads/images/0d7c2fa4-5583-4f3c-a7c2-822b826aadf1.webp b/backend/uploads/images/0d7c2fa4-5583-4f3c-a7c2-822b826aadf1.webp new file mode 100644 index 0000000..5b234e3 Binary files /dev/null and b/backend/uploads/images/0d7c2fa4-5583-4f3c-a7c2-822b826aadf1.webp differ diff --git a/backend/uploads/images/0ddf7146-52dc-4c54-818c-5bc65057bd32.webp b/backend/uploads/images/0ddf7146-52dc-4c54-818c-5bc65057bd32.webp new file mode 100644 index 0000000..5216070 Binary files /dev/null and b/backend/uploads/images/0ddf7146-52dc-4c54-818c-5bc65057bd32.webp differ diff --git a/backend/uploads/images/0de40150-8723-43b9-9b25-7128bc714e35.webp b/backend/uploads/images/0de40150-8723-43b9-9b25-7128bc714e35.webp new file mode 100644 index 0000000..b0e9aac Binary files /dev/null and b/backend/uploads/images/0de40150-8723-43b9-9b25-7128bc714e35.webp differ diff --git a/backend/uploads/images/0e05f307-2c60-4b34-999c-dd0084fa9b2b.webp b/backend/uploads/images/0e05f307-2c60-4b34-999c-dd0084fa9b2b.webp new file mode 100644 index 0000000..219d1c8 Binary files /dev/null and b/backend/uploads/images/0e05f307-2c60-4b34-999c-dd0084fa9b2b.webp differ diff --git a/backend/uploads/images/0e4db4e9-5999-4cc7-8fda-127d8c1f4ba4.webp b/backend/uploads/images/0e4db4e9-5999-4cc7-8fda-127d8c1f4ba4.webp new file mode 100644 index 0000000..cef0947 Binary files /dev/null and b/backend/uploads/images/0e4db4e9-5999-4cc7-8fda-127d8c1f4ba4.webp differ diff --git a/backend/uploads/images/0efb8313-0576-4381-a2ed-670e23556536.webp b/backend/uploads/images/0efb8313-0576-4381-a2ed-670e23556536.webp new file mode 100644 index 0000000..0746ebb Binary files /dev/null and b/backend/uploads/images/0efb8313-0576-4381-a2ed-670e23556536.webp differ diff --git a/backend/uploads/images/0f2424ef-43d5-436e-ad49-3beb1fdc24de.webp b/backend/uploads/images/0f2424ef-43d5-436e-ad49-3beb1fdc24de.webp new file mode 100644 index 0000000..eefd3dc Binary files /dev/null and b/backend/uploads/images/0f2424ef-43d5-436e-ad49-3beb1fdc24de.webp differ diff --git a/backend/uploads/images/0f4199d1-83e1-4777-ba59-bf7be202200a.webp b/backend/uploads/images/0f4199d1-83e1-4777-ba59-bf7be202200a.webp new file mode 100644 index 0000000..98cb645 Binary files /dev/null and b/backend/uploads/images/0f4199d1-83e1-4777-ba59-bf7be202200a.webp differ diff --git a/backend/uploads/images/0f4a2281-691a-412e-80ec-4e17abb54b20.webp b/backend/uploads/images/0f4a2281-691a-412e-80ec-4e17abb54b20.webp new file mode 100644 index 0000000..d9e7662 Binary files /dev/null and b/backend/uploads/images/0f4a2281-691a-412e-80ec-4e17abb54b20.webp differ diff --git a/backend/uploads/images/0f72c485-8213-494d-b109-2b1ebe87f3cd.webp b/backend/uploads/images/0f72c485-8213-494d-b109-2b1ebe87f3cd.webp new file mode 100644 index 0000000..7b82bd0 Binary files /dev/null and b/backend/uploads/images/0f72c485-8213-494d-b109-2b1ebe87f3cd.webp differ diff --git a/backend/uploads/images/0f9fd3b5-d72e-4a31-b2ec-975a7630f2fe.webp b/backend/uploads/images/0f9fd3b5-d72e-4a31-b2ec-975a7630f2fe.webp new file mode 100644 index 0000000..0de1eee Binary files /dev/null and b/backend/uploads/images/0f9fd3b5-d72e-4a31-b2ec-975a7630f2fe.webp differ diff --git a/backend/uploads/images/0feed471-24c9-45f0-918b-8fe75d89e856.webp b/backend/uploads/images/0feed471-24c9-45f0-918b-8fe75d89e856.webp new file mode 100644 index 0000000..6de1224 Binary files /dev/null and b/backend/uploads/images/0feed471-24c9-45f0-918b-8fe75d89e856.webp differ diff --git a/backend/uploads/images/106fceda-f621-4577-a38c-d04f33ce1c2d.webp b/backend/uploads/images/106fceda-f621-4577-a38c-d04f33ce1c2d.webp new file mode 100644 index 0000000..09d0b14 Binary files /dev/null and b/backend/uploads/images/106fceda-f621-4577-a38c-d04f33ce1c2d.webp differ diff --git a/backend/uploads/images/10e3df13-eda2-425a-a032-2da72555e381.webp b/backend/uploads/images/10e3df13-eda2-425a-a032-2da72555e381.webp new file mode 100644 index 0000000..855cdbf Binary files /dev/null and b/backend/uploads/images/10e3df13-eda2-425a-a032-2da72555e381.webp differ diff --git a/backend/uploads/images/11d93e4e-ef19-40df-a7db-0496c51dfac5.webp b/backend/uploads/images/11d93e4e-ef19-40df-a7db-0496c51dfac5.webp new file mode 100644 index 0000000..f90efc7 Binary files /dev/null and b/backend/uploads/images/11d93e4e-ef19-40df-a7db-0496c51dfac5.webp differ diff --git a/backend/uploads/images/126ab3a8-e1fe-46b6-973a-1212862ff3a3.webp b/backend/uploads/images/126ab3a8-e1fe-46b6-973a-1212862ff3a3.webp new file mode 100644 index 0000000..7f18860 Binary files /dev/null and b/backend/uploads/images/126ab3a8-e1fe-46b6-973a-1212862ff3a3.webp differ diff --git a/backend/uploads/images/1306aea6-e8b4-4cd6-8ebd-bbe0b1c07616.webp b/backend/uploads/images/1306aea6-e8b4-4cd6-8ebd-bbe0b1c07616.webp new file mode 100644 index 0000000..52b40e9 Binary files /dev/null and b/backend/uploads/images/1306aea6-e8b4-4cd6-8ebd-bbe0b1c07616.webp differ diff --git a/backend/uploads/images/131716eb-674a-4df6-b131-aeba3ec26bfd.webp b/backend/uploads/images/131716eb-674a-4df6-b131-aeba3ec26bfd.webp new file mode 100644 index 0000000..8af16ea Binary files /dev/null and b/backend/uploads/images/131716eb-674a-4df6-b131-aeba3ec26bfd.webp differ diff --git a/backend/uploads/images/134576bd-6764-4236-bdf3-3af4bbe63921.webp b/backend/uploads/images/134576bd-6764-4236-bdf3-3af4bbe63921.webp new file mode 100644 index 0000000..e5d2ac2 Binary files /dev/null and b/backend/uploads/images/134576bd-6764-4236-bdf3-3af4bbe63921.webp differ diff --git a/backend/uploads/images/135ac504-a896-4fd7-af9b-11894efb9d26.webp b/backend/uploads/images/135ac504-a896-4fd7-af9b-11894efb9d26.webp new file mode 100644 index 0000000..02a773a Binary files /dev/null and b/backend/uploads/images/135ac504-a896-4fd7-af9b-11894efb9d26.webp differ diff --git a/backend/uploads/images/139d2f7b-5a65-4a21-973e-f71964c913da.webp b/backend/uploads/images/139d2f7b-5a65-4a21-973e-f71964c913da.webp new file mode 100644 index 0000000..d789bca Binary files /dev/null and b/backend/uploads/images/139d2f7b-5a65-4a21-973e-f71964c913da.webp differ diff --git a/backend/uploads/images/142d6d10-9b17-42c7-8d0b-2d263c6528d7.webp b/backend/uploads/images/142d6d10-9b17-42c7-8d0b-2d263c6528d7.webp new file mode 100644 index 0000000..190be97 Binary files /dev/null and b/backend/uploads/images/142d6d10-9b17-42c7-8d0b-2d263c6528d7.webp differ diff --git a/backend/uploads/images/14a6104e-a410-4177-892c-b76176a49f43.webp b/backend/uploads/images/14a6104e-a410-4177-892c-b76176a49f43.webp new file mode 100644 index 0000000..bec3ee6 Binary files /dev/null and b/backend/uploads/images/14a6104e-a410-4177-892c-b76176a49f43.webp differ diff --git a/backend/uploads/images/14ef1c0d-5c3c-46ab-a399-c95d8fefe039.webp b/backend/uploads/images/14ef1c0d-5c3c-46ab-a399-c95d8fefe039.webp new file mode 100644 index 0000000..248df05 Binary files /dev/null and b/backend/uploads/images/14ef1c0d-5c3c-46ab-a399-c95d8fefe039.webp differ diff --git a/backend/uploads/images/14f2d026-f0b6-44c7-88e7-29d8e4438049.webp b/backend/uploads/images/14f2d026-f0b6-44c7-88e7-29d8e4438049.webp new file mode 100644 index 0000000..c5af680 Binary files /dev/null and b/backend/uploads/images/14f2d026-f0b6-44c7-88e7-29d8e4438049.webp differ diff --git a/backend/uploads/images/152263ee-7246-4d52-bc98-710708e0dd24.webp b/backend/uploads/images/152263ee-7246-4d52-bc98-710708e0dd24.webp new file mode 100644 index 0000000..7a8c68c Binary files /dev/null and b/backend/uploads/images/152263ee-7246-4d52-bc98-710708e0dd24.webp differ diff --git a/backend/uploads/images/1532404b-9414-4acf-ac0c-25d2e3df1e6a.webp b/backend/uploads/images/1532404b-9414-4acf-ac0c-25d2e3df1e6a.webp new file mode 100644 index 0000000..4c02c9a Binary files /dev/null and b/backend/uploads/images/1532404b-9414-4acf-ac0c-25d2e3df1e6a.webp differ diff --git a/backend/uploads/images/15428c15-67d6-4cf9-a167-2c8f563bdcdc.webp b/backend/uploads/images/15428c15-67d6-4cf9-a167-2c8f563bdcdc.webp new file mode 100644 index 0000000..3e0b8a2 Binary files /dev/null and b/backend/uploads/images/15428c15-67d6-4cf9-a167-2c8f563bdcdc.webp differ diff --git a/backend/uploads/images/15a3aee7-9607-4dc9-8a1e-e93e059ddce4.webp b/backend/uploads/images/15a3aee7-9607-4dc9-8a1e-e93e059ddce4.webp new file mode 100644 index 0000000..19a4ace Binary files /dev/null and b/backend/uploads/images/15a3aee7-9607-4dc9-8a1e-e93e059ddce4.webp differ diff --git a/backend/uploads/images/15adeba5-7a6a-4fd0-b624-f630b76fb8c9.webp b/backend/uploads/images/15adeba5-7a6a-4fd0-b624-f630b76fb8c9.webp new file mode 100644 index 0000000..703442c Binary files /dev/null and b/backend/uploads/images/15adeba5-7a6a-4fd0-b624-f630b76fb8c9.webp differ diff --git a/backend/uploads/images/15c06727-0350-42af-9cc8-9b595af76c91.webp b/backend/uploads/images/15c06727-0350-42af-9cc8-9b595af76c91.webp new file mode 100644 index 0000000..bafd7ab Binary files /dev/null and b/backend/uploads/images/15c06727-0350-42af-9cc8-9b595af76c91.webp differ diff --git a/backend/uploads/images/15fd6d77-5a69-4406-bf53-14109db96915.webp b/backend/uploads/images/15fd6d77-5a69-4406-bf53-14109db96915.webp new file mode 100644 index 0000000..c7c727b Binary files /dev/null and b/backend/uploads/images/15fd6d77-5a69-4406-bf53-14109db96915.webp differ diff --git a/backend/uploads/images/167caf37-b401-4b8f-af14-c5f6bde8ba61.webp b/backend/uploads/images/167caf37-b401-4b8f-af14-c5f6bde8ba61.webp new file mode 100644 index 0000000..af6a4f3 Binary files /dev/null and b/backend/uploads/images/167caf37-b401-4b8f-af14-c5f6bde8ba61.webp differ diff --git a/backend/uploads/images/16d268ae-0873-4d39-b3b1-033ad7323d3b.webp b/backend/uploads/images/16d268ae-0873-4d39-b3b1-033ad7323d3b.webp new file mode 100644 index 0000000..41ceac7 Binary files /dev/null and b/backend/uploads/images/16d268ae-0873-4d39-b3b1-033ad7323d3b.webp differ diff --git a/backend/uploads/images/16db0810-8fe3-4985-bc67-01797bcf4e3d.webp b/backend/uploads/images/16db0810-8fe3-4985-bc67-01797bcf4e3d.webp new file mode 100644 index 0000000..380da66 Binary files /dev/null and b/backend/uploads/images/16db0810-8fe3-4985-bc67-01797bcf4e3d.webp differ diff --git a/backend/uploads/images/170759e7-3cb4-4d99-82cc-46e85db1280a.webp b/backend/uploads/images/170759e7-3cb4-4d99-82cc-46e85db1280a.webp new file mode 100644 index 0000000..18d7f40 Binary files /dev/null and b/backend/uploads/images/170759e7-3cb4-4d99-82cc-46e85db1280a.webp differ diff --git a/backend/uploads/images/1723086e-1ac7-4a00-be7a-27c6cb34e5cb.webp b/backend/uploads/images/1723086e-1ac7-4a00-be7a-27c6cb34e5cb.webp new file mode 100644 index 0000000..cbc9332 Binary files /dev/null and b/backend/uploads/images/1723086e-1ac7-4a00-be7a-27c6cb34e5cb.webp differ diff --git a/backend/uploads/images/176aa534-3484-4a7d-b8af-73eceae145ee.webp b/backend/uploads/images/176aa534-3484-4a7d-b8af-73eceae145ee.webp new file mode 100644 index 0000000..0d48eb8 Binary files /dev/null and b/backend/uploads/images/176aa534-3484-4a7d-b8af-73eceae145ee.webp differ diff --git a/backend/uploads/images/17a8a5e9-0a96-49c8-a807-3170d6ebe8ef.webp b/backend/uploads/images/17a8a5e9-0a96-49c8-a807-3170d6ebe8ef.webp new file mode 100644 index 0000000..53df225 Binary files /dev/null and b/backend/uploads/images/17a8a5e9-0a96-49c8-a807-3170d6ebe8ef.webp differ diff --git a/backend/uploads/images/17c80033-3ba6-4532-be55-49f1f538bb26.webp b/backend/uploads/images/17c80033-3ba6-4532-be55-49f1f538bb26.webp new file mode 100644 index 0000000..1cca1d0 Binary files /dev/null and b/backend/uploads/images/17c80033-3ba6-4532-be55-49f1f538bb26.webp differ diff --git a/backend/uploads/images/18a0d0e2-a385-484a-89d6-86eb5a9d330d.webp b/backend/uploads/images/18a0d0e2-a385-484a-89d6-86eb5a9d330d.webp new file mode 100644 index 0000000..97ccdd0 Binary files /dev/null and b/backend/uploads/images/18a0d0e2-a385-484a-89d6-86eb5a9d330d.webp differ diff --git a/backend/uploads/images/18c730dc-c51e-4bdf-b74c-736bd79e4388.webp b/backend/uploads/images/18c730dc-c51e-4bdf-b74c-736bd79e4388.webp new file mode 100644 index 0000000..3ceafbe Binary files /dev/null and b/backend/uploads/images/18c730dc-c51e-4bdf-b74c-736bd79e4388.webp differ diff --git a/backend/uploads/images/18dc7b7f-6e19-4186-8d9b-0640b7617e79.webp b/backend/uploads/images/18dc7b7f-6e19-4186-8d9b-0640b7617e79.webp new file mode 100644 index 0000000..1ea36ff Binary files /dev/null and b/backend/uploads/images/18dc7b7f-6e19-4186-8d9b-0640b7617e79.webp differ diff --git a/backend/uploads/images/18e9d5b5-b9b8-4a21-8c83-6ddf1f1e4bd5.webp b/backend/uploads/images/18e9d5b5-b9b8-4a21-8c83-6ddf1f1e4bd5.webp new file mode 100644 index 0000000..0cd6e4f Binary files /dev/null and b/backend/uploads/images/18e9d5b5-b9b8-4a21-8c83-6ddf1f1e4bd5.webp differ diff --git a/backend/uploads/images/1938a805-5b39-4b20-bc52-faf184a147e5.webp b/backend/uploads/images/1938a805-5b39-4b20-bc52-faf184a147e5.webp new file mode 100644 index 0000000..79dbda0 Binary files /dev/null and b/backend/uploads/images/1938a805-5b39-4b20-bc52-faf184a147e5.webp differ diff --git a/backend/uploads/images/19f673df-d4b0-4d34-9b0e-5e3319d01a16.webp b/backend/uploads/images/19f673df-d4b0-4d34-9b0e-5e3319d01a16.webp new file mode 100644 index 0000000..ec410a9 Binary files /dev/null and b/backend/uploads/images/19f673df-d4b0-4d34-9b0e-5e3319d01a16.webp differ diff --git a/backend/uploads/images/1a2c641f-b5f5-4f87-a767-3b7b3a235bed.webp b/backend/uploads/images/1a2c641f-b5f5-4f87-a767-3b7b3a235bed.webp new file mode 100644 index 0000000..f6b7986 Binary files /dev/null and b/backend/uploads/images/1a2c641f-b5f5-4f87-a767-3b7b3a235bed.webp differ diff --git a/backend/uploads/images/1a4e9c06-c52a-46f8-ab8f-a785320bb872.webp b/backend/uploads/images/1a4e9c06-c52a-46f8-ab8f-a785320bb872.webp new file mode 100644 index 0000000..df48bd9 Binary files /dev/null and b/backend/uploads/images/1a4e9c06-c52a-46f8-ab8f-a785320bb872.webp differ diff --git a/backend/uploads/images/1a5266c1-42d1-47ae-9bf6-0edf7bf42c94.webp b/backend/uploads/images/1a5266c1-42d1-47ae-9bf6-0edf7bf42c94.webp new file mode 100644 index 0000000..96da59b Binary files /dev/null and b/backend/uploads/images/1a5266c1-42d1-47ae-9bf6-0edf7bf42c94.webp differ diff --git a/backend/uploads/images/1a791478-7f39-4e49-b089-ed22a84e3e01.webp b/backend/uploads/images/1a791478-7f39-4e49-b089-ed22a84e3e01.webp new file mode 100644 index 0000000..b3c6427 Binary files /dev/null and b/backend/uploads/images/1a791478-7f39-4e49-b089-ed22a84e3e01.webp differ diff --git a/backend/uploads/images/1bcdcc3f-d266-432c-9d28-26c2d75406dd.webp b/backend/uploads/images/1bcdcc3f-d266-432c-9d28-26c2d75406dd.webp new file mode 100644 index 0000000..cd68ab4 Binary files /dev/null and b/backend/uploads/images/1bcdcc3f-d266-432c-9d28-26c2d75406dd.webp differ diff --git a/backend/uploads/images/1bdef6b5-c781-43e4-8eee-161fca23993b.webp b/backend/uploads/images/1bdef6b5-c781-43e4-8eee-161fca23993b.webp new file mode 100644 index 0000000..642044c Binary files /dev/null and b/backend/uploads/images/1bdef6b5-c781-43e4-8eee-161fca23993b.webp differ diff --git a/backend/uploads/images/1cd6d2c6-d465-41b2-b44b-3b0dd29c78ac.webp b/backend/uploads/images/1cd6d2c6-d465-41b2-b44b-3b0dd29c78ac.webp new file mode 100644 index 0000000..d4342e7 Binary files /dev/null and b/backend/uploads/images/1cd6d2c6-d465-41b2-b44b-3b0dd29c78ac.webp differ diff --git a/backend/uploads/images/1d09f31d-6e49-4c1e-8416-08c57b42e6ac.webp b/backend/uploads/images/1d09f31d-6e49-4c1e-8416-08c57b42e6ac.webp new file mode 100644 index 0000000..a5b3419 Binary files /dev/null and b/backend/uploads/images/1d09f31d-6e49-4c1e-8416-08c57b42e6ac.webp differ diff --git a/backend/uploads/images/1d1cc855-a969-45da-98bb-1bc837f2d73a.webp b/backend/uploads/images/1d1cc855-a969-45da-98bb-1bc837f2d73a.webp new file mode 100644 index 0000000..74f3182 Binary files /dev/null and b/backend/uploads/images/1d1cc855-a969-45da-98bb-1bc837f2d73a.webp differ diff --git a/backend/uploads/images/1d828803-b784-456f-b3ec-3be1ebfc5b77.webp b/backend/uploads/images/1d828803-b784-456f-b3ec-3be1ebfc5b77.webp new file mode 100644 index 0000000..5abe7d5 Binary files /dev/null and b/backend/uploads/images/1d828803-b784-456f-b3ec-3be1ebfc5b77.webp differ diff --git a/backend/uploads/images/1de3ef72-b528-458e-9e00-b88450b45f5d.webp b/backend/uploads/images/1de3ef72-b528-458e-9e00-b88450b45f5d.webp new file mode 100644 index 0000000..7e1203c Binary files /dev/null and b/backend/uploads/images/1de3ef72-b528-458e-9e00-b88450b45f5d.webp differ diff --git a/backend/uploads/images/1f0e23a4-0740-4ccc-87f4-863abe1a4440.webp b/backend/uploads/images/1f0e23a4-0740-4ccc-87f4-863abe1a4440.webp new file mode 100644 index 0000000..756f00b Binary files /dev/null and b/backend/uploads/images/1f0e23a4-0740-4ccc-87f4-863abe1a4440.webp differ diff --git a/backend/uploads/images/1f78cecf-2d3b-499a-968f-f9ebee4feafb.webp b/backend/uploads/images/1f78cecf-2d3b-499a-968f-f9ebee4feafb.webp new file mode 100644 index 0000000..4b9fdbe Binary files /dev/null and b/backend/uploads/images/1f78cecf-2d3b-499a-968f-f9ebee4feafb.webp differ diff --git a/backend/uploads/images/1f935483-3722-46f7-b3c2-1c6af4267a88.webp b/backend/uploads/images/1f935483-3722-46f7-b3c2-1c6af4267a88.webp new file mode 100644 index 0000000..b77a600 Binary files /dev/null and b/backend/uploads/images/1f935483-3722-46f7-b3c2-1c6af4267a88.webp differ diff --git a/backend/uploads/images/1ff7eacb-9ade-4f4d-82c3-5ad16cf78b41.webp b/backend/uploads/images/1ff7eacb-9ade-4f4d-82c3-5ad16cf78b41.webp new file mode 100644 index 0000000..58d8915 Binary files /dev/null and b/backend/uploads/images/1ff7eacb-9ade-4f4d-82c3-5ad16cf78b41.webp differ diff --git a/backend/uploads/images/217e631a-f409-43bc-a1ee-94e04ee0ee8f.webp b/backend/uploads/images/217e631a-f409-43bc-a1ee-94e04ee0ee8f.webp new file mode 100644 index 0000000..eff66b5 Binary files /dev/null and b/backend/uploads/images/217e631a-f409-43bc-a1ee-94e04ee0ee8f.webp differ diff --git a/backend/uploads/images/2189c3d8-8a10-4cbd-ad95-ba791b93ea7b.webp b/backend/uploads/images/2189c3d8-8a10-4cbd-ad95-ba791b93ea7b.webp new file mode 100644 index 0000000..f7d94d1 Binary files /dev/null and b/backend/uploads/images/2189c3d8-8a10-4cbd-ad95-ba791b93ea7b.webp differ diff --git a/backend/uploads/images/224c8318-bd85-485c-a205-fd92aab9a8c5.webp b/backend/uploads/images/224c8318-bd85-485c-a205-fd92aab9a8c5.webp new file mode 100644 index 0000000..37adef1 Binary files /dev/null and b/backend/uploads/images/224c8318-bd85-485c-a205-fd92aab9a8c5.webp differ diff --git a/backend/uploads/images/22e02e09-6a45-4774-ae49-4ee9b4496897.webp b/backend/uploads/images/22e02e09-6a45-4774-ae49-4ee9b4496897.webp new file mode 100644 index 0000000..1cfbdf2 Binary files /dev/null and b/backend/uploads/images/22e02e09-6a45-4774-ae49-4ee9b4496897.webp differ diff --git a/backend/uploads/images/23ae8124-9dc7-4c96-a135-4b511dfd83b4.webp b/backend/uploads/images/23ae8124-9dc7-4c96-a135-4b511dfd83b4.webp new file mode 100644 index 0000000..cf4ac86 Binary files /dev/null and b/backend/uploads/images/23ae8124-9dc7-4c96-a135-4b511dfd83b4.webp differ diff --git a/backend/uploads/images/2410874f-2b7b-40f1-9deb-c300631b881d.webp b/backend/uploads/images/2410874f-2b7b-40f1-9deb-c300631b881d.webp new file mode 100644 index 0000000..724610f Binary files /dev/null and b/backend/uploads/images/2410874f-2b7b-40f1-9deb-c300631b881d.webp differ diff --git a/backend/uploads/images/24b9f534-214c-4e6d-bebd-881253e9841a.webp b/backend/uploads/images/24b9f534-214c-4e6d-bebd-881253e9841a.webp new file mode 100644 index 0000000..1665de8 Binary files /dev/null and b/backend/uploads/images/24b9f534-214c-4e6d-bebd-881253e9841a.webp differ diff --git a/backend/uploads/images/24c1cc02-71a3-457f-8df3-02f3ab44c3d5.webp b/backend/uploads/images/24c1cc02-71a3-457f-8df3-02f3ab44c3d5.webp new file mode 100644 index 0000000..4e2f593 Binary files /dev/null and b/backend/uploads/images/24c1cc02-71a3-457f-8df3-02f3ab44c3d5.webp differ diff --git a/backend/uploads/images/25b339ad-9482-4c44-97ae-c2a3a680763d.webp b/backend/uploads/images/25b339ad-9482-4c44-97ae-c2a3a680763d.webp new file mode 100644 index 0000000..8f972a3 Binary files /dev/null and b/backend/uploads/images/25b339ad-9482-4c44-97ae-c2a3a680763d.webp differ diff --git a/backend/uploads/images/25cd6089-8bea-49b0-a24f-d8a7dc37283a.webp b/backend/uploads/images/25cd6089-8bea-49b0-a24f-d8a7dc37283a.webp new file mode 100644 index 0000000..2ae4e4a Binary files /dev/null and b/backend/uploads/images/25cd6089-8bea-49b0-a24f-d8a7dc37283a.webp differ diff --git a/backend/uploads/images/260fd173-6ad3-4c39-b169-22bd744ffc7c.webp b/backend/uploads/images/260fd173-6ad3-4c39-b169-22bd744ffc7c.webp new file mode 100644 index 0000000..56ff6d5 Binary files /dev/null and b/backend/uploads/images/260fd173-6ad3-4c39-b169-22bd744ffc7c.webp differ diff --git a/backend/uploads/images/261bc1b5-86ad-4d42-97b6-8d87a70f4410.webp b/backend/uploads/images/261bc1b5-86ad-4d42-97b6-8d87a70f4410.webp new file mode 100644 index 0000000..c64928b Binary files /dev/null and b/backend/uploads/images/261bc1b5-86ad-4d42-97b6-8d87a70f4410.webp differ diff --git a/backend/uploads/images/26a0041e-6e07-4818-8165-ef11bf0a57e9.webp b/backend/uploads/images/26a0041e-6e07-4818-8165-ef11bf0a57e9.webp new file mode 100644 index 0000000..cb4a673 Binary files /dev/null and b/backend/uploads/images/26a0041e-6e07-4818-8165-ef11bf0a57e9.webp differ diff --git a/backend/uploads/images/26abc5bf-a880-45d5-9776-769b05e1a2d0.webp b/backend/uploads/images/26abc5bf-a880-45d5-9776-769b05e1a2d0.webp new file mode 100644 index 0000000..098d201 Binary files /dev/null and b/backend/uploads/images/26abc5bf-a880-45d5-9776-769b05e1a2d0.webp differ diff --git a/backend/uploads/images/26d5b523-a39d-4d47-9ead-223d8a2f2819.webp b/backend/uploads/images/26d5b523-a39d-4d47-9ead-223d8a2f2819.webp new file mode 100644 index 0000000..9459a28 Binary files /dev/null and b/backend/uploads/images/26d5b523-a39d-4d47-9ead-223d8a2f2819.webp differ diff --git a/backend/uploads/images/26fad3f1-862e-43bf-ab97-a7be609f2f92.webp b/backend/uploads/images/26fad3f1-862e-43bf-ab97-a7be609f2f92.webp new file mode 100644 index 0000000..77ed438 Binary files /dev/null and b/backend/uploads/images/26fad3f1-862e-43bf-ab97-a7be609f2f92.webp differ diff --git a/backend/uploads/images/28573e60-9b6d-4879-98f3-a39986230430.webp b/backend/uploads/images/28573e60-9b6d-4879-98f3-a39986230430.webp new file mode 100644 index 0000000..28ee7dc Binary files /dev/null and b/backend/uploads/images/28573e60-9b6d-4879-98f3-a39986230430.webp differ diff --git a/backend/uploads/images/28585ee3-6a4a-4d89-b95a-87f473e8f6ee.webp b/backend/uploads/images/28585ee3-6a4a-4d89-b95a-87f473e8f6ee.webp new file mode 100644 index 0000000..ab97f81 Binary files /dev/null and b/backend/uploads/images/28585ee3-6a4a-4d89-b95a-87f473e8f6ee.webp differ diff --git a/backend/uploads/images/286697a5-5487-44b1-b754-30fc02fed6bc.webp b/backend/uploads/images/286697a5-5487-44b1-b754-30fc02fed6bc.webp new file mode 100644 index 0000000..ba09df6 Binary files /dev/null and b/backend/uploads/images/286697a5-5487-44b1-b754-30fc02fed6bc.webp differ diff --git a/backend/uploads/images/28aa20dc-30f9-49ea-94e4-39c10a4144f7.webp b/backend/uploads/images/28aa20dc-30f9-49ea-94e4-39c10a4144f7.webp new file mode 100644 index 0000000..e0511eb Binary files /dev/null and b/backend/uploads/images/28aa20dc-30f9-49ea-94e4-39c10a4144f7.webp differ diff --git a/backend/uploads/images/29170595-ddd0-4982-bfe6-6bcf33cc7652.webp b/backend/uploads/images/29170595-ddd0-4982-bfe6-6bcf33cc7652.webp new file mode 100644 index 0000000..5f07d3e Binary files /dev/null and b/backend/uploads/images/29170595-ddd0-4982-bfe6-6bcf33cc7652.webp differ diff --git a/backend/uploads/images/292f1df1-dcd2-4227-8114-b32c1904124a.webp b/backend/uploads/images/292f1df1-dcd2-4227-8114-b32c1904124a.webp new file mode 100644 index 0000000..8e39a89 Binary files /dev/null and b/backend/uploads/images/292f1df1-dcd2-4227-8114-b32c1904124a.webp differ diff --git a/backend/uploads/images/29677af2-76eb-4778-a129-bfea5ae82189.webp b/backend/uploads/images/29677af2-76eb-4778-a129-bfea5ae82189.webp new file mode 100644 index 0000000..78f0536 Binary files /dev/null and b/backend/uploads/images/29677af2-76eb-4778-a129-bfea5ae82189.webp differ diff --git a/backend/uploads/images/2a45be5b-45d6-4d69-bc29-38f44013e822.webp b/backend/uploads/images/2a45be5b-45d6-4d69-bc29-38f44013e822.webp new file mode 100644 index 0000000..2c7a794 Binary files /dev/null and b/backend/uploads/images/2a45be5b-45d6-4d69-bc29-38f44013e822.webp differ diff --git a/backend/uploads/images/2acaad6b-a171-47f0-8a1f-0b5f99646c26.webp b/backend/uploads/images/2acaad6b-a171-47f0-8a1f-0b5f99646c26.webp new file mode 100644 index 0000000..763ba14 Binary files /dev/null and b/backend/uploads/images/2acaad6b-a171-47f0-8a1f-0b5f99646c26.webp differ diff --git a/backend/uploads/images/2ae06679-24bf-404f-814c-a3a5c6a8a8c9.webp b/backend/uploads/images/2ae06679-24bf-404f-814c-a3a5c6a8a8c9.webp new file mode 100644 index 0000000..b16d23f Binary files /dev/null and b/backend/uploads/images/2ae06679-24bf-404f-814c-a3a5c6a8a8c9.webp differ diff --git a/backend/uploads/images/2b04b6db-e617-478f-b985-5db16297bbd2.webp b/backend/uploads/images/2b04b6db-e617-478f-b985-5db16297bbd2.webp new file mode 100644 index 0000000..ab8a9e1 Binary files /dev/null and b/backend/uploads/images/2b04b6db-e617-478f-b985-5db16297bbd2.webp differ diff --git a/backend/uploads/images/2b2717fa-8876-4cd1-b5f7-617244275b8c.webp b/backend/uploads/images/2b2717fa-8876-4cd1-b5f7-617244275b8c.webp new file mode 100644 index 0000000..e976fde Binary files /dev/null and b/backend/uploads/images/2b2717fa-8876-4cd1-b5f7-617244275b8c.webp differ diff --git a/backend/uploads/images/2b3299f2-f899-47af-a8e7-de0ab316ef28.webp b/backend/uploads/images/2b3299f2-f899-47af-a8e7-de0ab316ef28.webp new file mode 100644 index 0000000..a41d18a Binary files /dev/null and b/backend/uploads/images/2b3299f2-f899-47af-a8e7-de0ab316ef28.webp differ diff --git a/backend/uploads/images/2bcf807b-e65b-495a-8c16-4cee8ea9c914.webp b/backend/uploads/images/2bcf807b-e65b-495a-8c16-4cee8ea9c914.webp new file mode 100644 index 0000000..3578715 Binary files /dev/null and b/backend/uploads/images/2bcf807b-e65b-495a-8c16-4cee8ea9c914.webp differ diff --git a/backend/uploads/images/2c006863-ce6d-4265-b030-bbbf068b7d3e.webp b/backend/uploads/images/2c006863-ce6d-4265-b030-bbbf068b7d3e.webp new file mode 100644 index 0000000..78da6db Binary files /dev/null and b/backend/uploads/images/2c006863-ce6d-4265-b030-bbbf068b7d3e.webp differ diff --git a/backend/uploads/images/2c2545dd-dc3d-439e-a9ce-0ded22003a86.webp b/backend/uploads/images/2c2545dd-dc3d-439e-a9ce-0ded22003a86.webp new file mode 100644 index 0000000..47433d0 Binary files /dev/null and b/backend/uploads/images/2c2545dd-dc3d-439e-a9ce-0ded22003a86.webp differ diff --git a/backend/uploads/images/2dd13de9-9bf3-4246-a448-7aa90f0d7485.webp b/backend/uploads/images/2dd13de9-9bf3-4246-a448-7aa90f0d7485.webp new file mode 100644 index 0000000..f26d566 Binary files /dev/null and b/backend/uploads/images/2dd13de9-9bf3-4246-a448-7aa90f0d7485.webp differ diff --git a/backend/uploads/images/2ecaac23-6538-4673-8132-19d789715a33.webp b/backend/uploads/images/2ecaac23-6538-4673-8132-19d789715a33.webp new file mode 100644 index 0000000..d7476be Binary files /dev/null and b/backend/uploads/images/2ecaac23-6538-4673-8132-19d789715a33.webp differ diff --git a/backend/uploads/images/2ed040a1-7a61-46f0-aed1-2999d91c6275.webp b/backend/uploads/images/2ed040a1-7a61-46f0-aed1-2999d91c6275.webp new file mode 100644 index 0000000..22961ac Binary files /dev/null and b/backend/uploads/images/2ed040a1-7a61-46f0-aed1-2999d91c6275.webp differ diff --git a/backend/uploads/images/2f805c14-d970-4e50-9ac0-31becf9bdd5f.webp b/backend/uploads/images/2f805c14-d970-4e50-9ac0-31becf9bdd5f.webp new file mode 100644 index 0000000..bc6f220 Binary files /dev/null and b/backend/uploads/images/2f805c14-d970-4e50-9ac0-31becf9bdd5f.webp differ diff --git a/backend/uploads/images/2fc6155a-d83d-4bd5-9a92-d4eeea1b0c96.webp b/backend/uploads/images/2fc6155a-d83d-4bd5-9a92-d4eeea1b0c96.webp new file mode 100644 index 0000000..b33bb84 Binary files /dev/null and b/backend/uploads/images/2fc6155a-d83d-4bd5-9a92-d4eeea1b0c96.webp differ diff --git a/backend/uploads/images/30036cd3-0954-4240-a6ea-77cb33f7242b.webp b/backend/uploads/images/30036cd3-0954-4240-a6ea-77cb33f7242b.webp new file mode 100644 index 0000000..0b35ef9 Binary files /dev/null and b/backend/uploads/images/30036cd3-0954-4240-a6ea-77cb33f7242b.webp differ diff --git a/backend/uploads/images/30afb248-8ae9-4fe7-92fb-a14641e66856.webp b/backend/uploads/images/30afb248-8ae9-4fe7-92fb-a14641e66856.webp new file mode 100644 index 0000000..d3d8394 Binary files /dev/null and b/backend/uploads/images/30afb248-8ae9-4fe7-92fb-a14641e66856.webp differ diff --git a/backend/uploads/images/30eab22e-d4ce-4113-aeff-966676f03c59.webp b/backend/uploads/images/30eab22e-d4ce-4113-aeff-966676f03c59.webp new file mode 100644 index 0000000..e22e62e Binary files /dev/null and b/backend/uploads/images/30eab22e-d4ce-4113-aeff-966676f03c59.webp differ diff --git a/backend/uploads/images/322cb54e-f7ca-4e74-9321-e46b3acdbc9c.webp b/backend/uploads/images/322cb54e-f7ca-4e74-9321-e46b3acdbc9c.webp new file mode 100644 index 0000000..7164f9e Binary files /dev/null and b/backend/uploads/images/322cb54e-f7ca-4e74-9321-e46b3acdbc9c.webp differ diff --git a/backend/uploads/images/33291a0e-9758-4b19-9dea-a6e1bdd94ec4.webp b/backend/uploads/images/33291a0e-9758-4b19-9dea-a6e1bdd94ec4.webp new file mode 100644 index 0000000..be255e5 Binary files /dev/null and b/backend/uploads/images/33291a0e-9758-4b19-9dea-a6e1bdd94ec4.webp differ diff --git a/backend/uploads/images/334773b6-1bf2-4429-845d-97fada111479.webp b/backend/uploads/images/334773b6-1bf2-4429-845d-97fada111479.webp new file mode 100644 index 0000000..3ffd799 Binary files /dev/null and b/backend/uploads/images/334773b6-1bf2-4429-845d-97fada111479.webp differ diff --git a/backend/uploads/images/334c0ea5-60c3-4f97-9b77-53c0dd28965c.webp b/backend/uploads/images/334c0ea5-60c3-4f97-9b77-53c0dd28965c.webp new file mode 100644 index 0000000..e6f82ae Binary files /dev/null and b/backend/uploads/images/334c0ea5-60c3-4f97-9b77-53c0dd28965c.webp differ diff --git a/backend/uploads/images/34d09280-d654-43f0-b032-0357a585ab90.webp b/backend/uploads/images/34d09280-d654-43f0-b032-0357a585ab90.webp new file mode 100644 index 0000000..cd7925a Binary files /dev/null and b/backend/uploads/images/34d09280-d654-43f0-b032-0357a585ab90.webp differ diff --git a/backend/uploads/images/354ad92b-6452-4818-979e-a3269a305d99.webp b/backend/uploads/images/354ad92b-6452-4818-979e-a3269a305d99.webp new file mode 100644 index 0000000..efa8a95 Binary files /dev/null and b/backend/uploads/images/354ad92b-6452-4818-979e-a3269a305d99.webp differ diff --git a/backend/uploads/images/355edc4d-a536-4205-8894-c956eee6dd52.webp b/backend/uploads/images/355edc4d-a536-4205-8894-c956eee6dd52.webp new file mode 100644 index 0000000..30b4b5d Binary files /dev/null and b/backend/uploads/images/355edc4d-a536-4205-8894-c956eee6dd52.webp differ diff --git a/backend/uploads/images/35b67d75-4a67-4969-b7bb-fb3d4b6ed6bd.webp b/backend/uploads/images/35b67d75-4a67-4969-b7bb-fb3d4b6ed6bd.webp new file mode 100644 index 0000000..d46e8ce Binary files /dev/null and b/backend/uploads/images/35b67d75-4a67-4969-b7bb-fb3d4b6ed6bd.webp differ diff --git a/backend/uploads/images/361b333d-ed68-40c2-b92d-d6468f539e0c.webp b/backend/uploads/images/361b333d-ed68-40c2-b92d-d6468f539e0c.webp new file mode 100644 index 0000000..208948a Binary files /dev/null and b/backend/uploads/images/361b333d-ed68-40c2-b92d-d6468f539e0c.webp differ diff --git a/backend/uploads/images/362a6bc0-0648-4c16-bfcf-cfa9194d90fa.webp b/backend/uploads/images/362a6bc0-0648-4c16-bfcf-cfa9194d90fa.webp new file mode 100644 index 0000000..61e418c Binary files /dev/null and b/backend/uploads/images/362a6bc0-0648-4c16-bfcf-cfa9194d90fa.webp differ diff --git a/backend/uploads/images/36981cb4-9674-43a2-ae58-df46bfc3922b.webp b/backend/uploads/images/36981cb4-9674-43a2-ae58-df46bfc3922b.webp new file mode 100644 index 0000000..de34350 Binary files /dev/null and b/backend/uploads/images/36981cb4-9674-43a2-ae58-df46bfc3922b.webp differ diff --git a/backend/uploads/images/36a4d73a-5e2e-4700-8026-d35dd0048d24.webp b/backend/uploads/images/36a4d73a-5e2e-4700-8026-d35dd0048d24.webp new file mode 100644 index 0000000..36aaa89 Binary files /dev/null and b/backend/uploads/images/36a4d73a-5e2e-4700-8026-d35dd0048d24.webp differ diff --git a/backend/uploads/images/36f3dc13-a66a-4d3e-86fe-46555b424ae5.webp b/backend/uploads/images/36f3dc13-a66a-4d3e-86fe-46555b424ae5.webp new file mode 100644 index 0000000..31769aa Binary files /dev/null and b/backend/uploads/images/36f3dc13-a66a-4d3e-86fe-46555b424ae5.webp differ diff --git a/backend/uploads/images/37449a42-70f0-4c33-8f35-75dfc1730651.webp b/backend/uploads/images/37449a42-70f0-4c33-8f35-75dfc1730651.webp new file mode 100644 index 0000000..7932616 Binary files /dev/null and b/backend/uploads/images/37449a42-70f0-4c33-8f35-75dfc1730651.webp differ diff --git a/backend/uploads/images/374bc24b-7378-4e62-ad9e-a3970148b85a.webp b/backend/uploads/images/374bc24b-7378-4e62-ad9e-a3970148b85a.webp new file mode 100644 index 0000000..78817ba Binary files /dev/null and b/backend/uploads/images/374bc24b-7378-4e62-ad9e-a3970148b85a.webp differ diff --git a/backend/uploads/images/376d4861-3b43-4f8c-ac11-17cf289372eb.webp b/backend/uploads/images/376d4861-3b43-4f8c-ac11-17cf289372eb.webp new file mode 100644 index 0000000..571b539 Binary files /dev/null and b/backend/uploads/images/376d4861-3b43-4f8c-ac11-17cf289372eb.webp differ diff --git a/backend/uploads/images/37ac72aa-6671-4a25-a715-0e670511cbcd.webp b/backend/uploads/images/37ac72aa-6671-4a25-a715-0e670511cbcd.webp new file mode 100644 index 0000000..b971f11 Binary files /dev/null and b/backend/uploads/images/37ac72aa-6671-4a25-a715-0e670511cbcd.webp differ diff --git a/backend/uploads/images/37c08d3a-9a33-405f-85c1-1536964ca509.webp b/backend/uploads/images/37c08d3a-9a33-405f-85c1-1536964ca509.webp new file mode 100644 index 0000000..43f95a4 Binary files /dev/null and b/backend/uploads/images/37c08d3a-9a33-405f-85c1-1536964ca509.webp differ diff --git a/backend/uploads/images/37ea9f0c-5031-41ca-9ff0-7b96470ac556.webp b/backend/uploads/images/37ea9f0c-5031-41ca-9ff0-7b96470ac556.webp new file mode 100644 index 0000000..7eea2b0 Binary files /dev/null and b/backend/uploads/images/37ea9f0c-5031-41ca-9ff0-7b96470ac556.webp differ diff --git a/backend/uploads/images/3812a014-0ee4-48eb-a0a6-751791578415.webp b/backend/uploads/images/3812a014-0ee4-48eb-a0a6-751791578415.webp new file mode 100644 index 0000000..19346cf Binary files /dev/null and b/backend/uploads/images/3812a014-0ee4-48eb-a0a6-751791578415.webp differ diff --git a/backend/uploads/images/383ab1b4-9800-4244-b34d-452688b32bde.webp b/backend/uploads/images/383ab1b4-9800-4244-b34d-452688b32bde.webp new file mode 100644 index 0000000..c6b7ff9 Binary files /dev/null and b/backend/uploads/images/383ab1b4-9800-4244-b34d-452688b32bde.webp differ diff --git a/backend/uploads/images/385af1c7-e74e-4fb3-8eae-6de67ac0f218.webp b/backend/uploads/images/385af1c7-e74e-4fb3-8eae-6de67ac0f218.webp new file mode 100644 index 0000000..3f23231 Binary files /dev/null and b/backend/uploads/images/385af1c7-e74e-4fb3-8eae-6de67ac0f218.webp differ diff --git a/backend/uploads/images/38884889-15f3-46e9-8371-028534833c21.webp b/backend/uploads/images/38884889-15f3-46e9-8371-028534833c21.webp new file mode 100644 index 0000000..08b9f5c Binary files /dev/null and b/backend/uploads/images/38884889-15f3-46e9-8371-028534833c21.webp differ diff --git a/backend/uploads/images/38df7847-4f8e-40e0-97fe-44e77dd36e4d.webp b/backend/uploads/images/38df7847-4f8e-40e0-97fe-44e77dd36e4d.webp new file mode 100644 index 0000000..9137970 Binary files /dev/null and b/backend/uploads/images/38df7847-4f8e-40e0-97fe-44e77dd36e4d.webp differ diff --git a/backend/uploads/images/391444c4-80c7-4189-95a0-eefdcd874a36.webp b/backend/uploads/images/391444c4-80c7-4189-95a0-eefdcd874a36.webp new file mode 100644 index 0000000..4e87eb9 Binary files /dev/null and b/backend/uploads/images/391444c4-80c7-4189-95a0-eefdcd874a36.webp differ diff --git a/backend/uploads/images/392441be-889e-4e01-a65b-f63c5dd25310.webp b/backend/uploads/images/392441be-889e-4e01-a65b-f63c5dd25310.webp new file mode 100644 index 0000000..63bc384 Binary files /dev/null and b/backend/uploads/images/392441be-889e-4e01-a65b-f63c5dd25310.webp differ diff --git a/backend/uploads/images/3958bc71-b6ad-4807-95ab-5cc65fba371a.webp b/backend/uploads/images/3958bc71-b6ad-4807-95ab-5cc65fba371a.webp new file mode 100644 index 0000000..8adb5cf Binary files /dev/null and b/backend/uploads/images/3958bc71-b6ad-4807-95ab-5cc65fba371a.webp differ diff --git a/backend/uploads/images/39b46093-5d26-4e55-a6b0-2257ecb08b2a.webp b/backend/uploads/images/39b46093-5d26-4e55-a6b0-2257ecb08b2a.webp new file mode 100644 index 0000000..c345e37 Binary files /dev/null and b/backend/uploads/images/39b46093-5d26-4e55-a6b0-2257ecb08b2a.webp differ diff --git a/backend/uploads/images/39b462b3-0297-42ad-8b51-3096d9babade.webp b/backend/uploads/images/39b462b3-0297-42ad-8b51-3096d9babade.webp new file mode 100644 index 0000000..ac2ecdf Binary files /dev/null and b/backend/uploads/images/39b462b3-0297-42ad-8b51-3096d9babade.webp differ diff --git a/backend/uploads/images/39b8b777-41b7-4fe7-86dc-927aec5bb7e1.webp b/backend/uploads/images/39b8b777-41b7-4fe7-86dc-927aec5bb7e1.webp new file mode 100644 index 0000000..b0c991e Binary files /dev/null and b/backend/uploads/images/39b8b777-41b7-4fe7-86dc-927aec5bb7e1.webp differ diff --git a/backend/uploads/images/3a3323df-2f01-4ee4-b45f-66cdc3605dde.webp b/backend/uploads/images/3a3323df-2f01-4ee4-b45f-66cdc3605dde.webp new file mode 100644 index 0000000..f145fa5 Binary files /dev/null and b/backend/uploads/images/3a3323df-2f01-4ee4-b45f-66cdc3605dde.webp differ diff --git a/backend/uploads/images/3a5af7db-6ae5-4b6c-8ba1-7d022ae8016e.webp b/backend/uploads/images/3a5af7db-6ae5-4b6c-8ba1-7d022ae8016e.webp new file mode 100644 index 0000000..7770b5e Binary files /dev/null and b/backend/uploads/images/3a5af7db-6ae5-4b6c-8ba1-7d022ae8016e.webp differ diff --git a/backend/uploads/images/3a649eda-69c7-4272-b358-648f4db41b56.webp b/backend/uploads/images/3a649eda-69c7-4272-b358-648f4db41b56.webp new file mode 100644 index 0000000..64e3176 Binary files /dev/null and b/backend/uploads/images/3a649eda-69c7-4272-b358-648f4db41b56.webp differ diff --git a/backend/uploads/images/3ab56af9-5a7f-4f24-b6f0-49e9bedad000.webp b/backend/uploads/images/3ab56af9-5a7f-4f24-b6f0-49e9bedad000.webp new file mode 100644 index 0000000..0876569 Binary files /dev/null and b/backend/uploads/images/3ab56af9-5a7f-4f24-b6f0-49e9bedad000.webp differ diff --git a/backend/uploads/images/3b41eb7c-34a5-4598-b8da-bc6aeff06056.webp b/backend/uploads/images/3b41eb7c-34a5-4598-b8da-bc6aeff06056.webp new file mode 100644 index 0000000..2319da0 Binary files /dev/null and b/backend/uploads/images/3b41eb7c-34a5-4598-b8da-bc6aeff06056.webp differ diff --git a/backend/uploads/images/3b9a3ec6-b14a-4707-81f3-e303ea792a96.webp b/backend/uploads/images/3b9a3ec6-b14a-4707-81f3-e303ea792a96.webp new file mode 100644 index 0000000..9630ec2 Binary files /dev/null and b/backend/uploads/images/3b9a3ec6-b14a-4707-81f3-e303ea792a96.webp differ diff --git a/backend/uploads/images/3c39bab7-e092-40ab-a467-1ee245a9e1b9.webp b/backend/uploads/images/3c39bab7-e092-40ab-a467-1ee245a9e1b9.webp new file mode 100644 index 0000000..5c82cba Binary files /dev/null and b/backend/uploads/images/3c39bab7-e092-40ab-a467-1ee245a9e1b9.webp differ diff --git a/backend/uploads/images/3c8fc6c7-f429-491f-8218-1b1d8a270e63.webp b/backend/uploads/images/3c8fc6c7-f429-491f-8218-1b1d8a270e63.webp new file mode 100644 index 0000000..2e3bfb3 Binary files /dev/null and b/backend/uploads/images/3c8fc6c7-f429-491f-8218-1b1d8a270e63.webp differ diff --git a/backend/uploads/images/3cec55d7-04e3-4d57-8b08-ad5e6000a918.webp b/backend/uploads/images/3cec55d7-04e3-4d57-8b08-ad5e6000a918.webp new file mode 100644 index 0000000..2874a45 Binary files /dev/null and b/backend/uploads/images/3cec55d7-04e3-4d57-8b08-ad5e6000a918.webp differ diff --git a/backend/uploads/images/3d43045a-cbf8-415a-9890-cbe7dd9c6d7c.webp b/backend/uploads/images/3d43045a-cbf8-415a-9890-cbe7dd9c6d7c.webp new file mode 100644 index 0000000..ee90a09 Binary files /dev/null and b/backend/uploads/images/3d43045a-cbf8-415a-9890-cbe7dd9c6d7c.webp differ diff --git a/backend/uploads/images/3d896317-6f95-4b6a-b85b-aaf67bc5baa5.webp b/backend/uploads/images/3d896317-6f95-4b6a-b85b-aaf67bc5baa5.webp new file mode 100644 index 0000000..88628d6 Binary files /dev/null and b/backend/uploads/images/3d896317-6f95-4b6a-b85b-aaf67bc5baa5.webp differ diff --git a/backend/uploads/images/3d9787d9-a842-4f3f-81ba-8bd2ad797993.webp b/backend/uploads/images/3d9787d9-a842-4f3f-81ba-8bd2ad797993.webp new file mode 100644 index 0000000..c014029 Binary files /dev/null and b/backend/uploads/images/3d9787d9-a842-4f3f-81ba-8bd2ad797993.webp differ diff --git a/backend/uploads/images/3dd112ea-e261-42b4-8645-2e8f942ac1f9.webp b/backend/uploads/images/3dd112ea-e261-42b4-8645-2e8f942ac1f9.webp new file mode 100644 index 0000000..6f78725 Binary files /dev/null and b/backend/uploads/images/3dd112ea-e261-42b4-8645-2e8f942ac1f9.webp differ diff --git a/backend/uploads/images/3df60aa0-b793-4c13-9881-12254df9317b.webp b/backend/uploads/images/3df60aa0-b793-4c13-9881-12254df9317b.webp new file mode 100644 index 0000000..a252118 Binary files /dev/null and b/backend/uploads/images/3df60aa0-b793-4c13-9881-12254df9317b.webp differ diff --git a/backend/uploads/images/3e4c2d49-2b28-475e-9186-09ba29cea5ef.webp b/backend/uploads/images/3e4c2d49-2b28-475e-9186-09ba29cea5ef.webp new file mode 100644 index 0000000..891c3e2 Binary files /dev/null and b/backend/uploads/images/3e4c2d49-2b28-475e-9186-09ba29cea5ef.webp differ diff --git a/backend/uploads/images/3ebd3b59-751c-43f2-b599-06935b010de0.webp b/backend/uploads/images/3ebd3b59-751c-43f2-b599-06935b010de0.webp new file mode 100644 index 0000000..d5ab039 Binary files /dev/null and b/backend/uploads/images/3ebd3b59-751c-43f2-b599-06935b010de0.webp differ diff --git a/backend/uploads/images/3ecfed70-b432-41b2-afbc-dc4c97ea63c9.webp b/backend/uploads/images/3ecfed70-b432-41b2-afbc-dc4c97ea63c9.webp new file mode 100644 index 0000000..e6fd7cf Binary files /dev/null and b/backend/uploads/images/3ecfed70-b432-41b2-afbc-dc4c97ea63c9.webp differ diff --git a/backend/uploads/images/3f2d6303-d5fc-4865-900d-95fe3ace4ab8.webp b/backend/uploads/images/3f2d6303-d5fc-4865-900d-95fe3ace4ab8.webp new file mode 100644 index 0000000..54a7ef9 Binary files /dev/null and b/backend/uploads/images/3f2d6303-d5fc-4865-900d-95fe3ace4ab8.webp differ diff --git a/backend/uploads/images/3f33c930-6f52-454e-81b4-d48e88e3bbdf.webp b/backend/uploads/images/3f33c930-6f52-454e-81b4-d48e88e3bbdf.webp new file mode 100644 index 0000000..a732771 Binary files /dev/null and b/backend/uploads/images/3f33c930-6f52-454e-81b4-d48e88e3bbdf.webp differ diff --git a/backend/uploads/images/3f856c13-3247-4e2e-b20e-9000f6cc8092.webp b/backend/uploads/images/3f856c13-3247-4e2e-b20e-9000f6cc8092.webp new file mode 100644 index 0000000..e6f3151 Binary files /dev/null and b/backend/uploads/images/3f856c13-3247-4e2e-b20e-9000f6cc8092.webp differ diff --git a/backend/uploads/images/3fb7ac1d-6987-45ee-9cd5-4ad33ab21884.webp b/backend/uploads/images/3fb7ac1d-6987-45ee-9cd5-4ad33ab21884.webp new file mode 100644 index 0000000..38d2a97 Binary files /dev/null and b/backend/uploads/images/3fb7ac1d-6987-45ee-9cd5-4ad33ab21884.webp differ diff --git a/backend/uploads/images/3fede355-6aef-447b-ad40-5178adfe07d0.webp b/backend/uploads/images/3fede355-6aef-447b-ad40-5178adfe07d0.webp new file mode 100644 index 0000000..3f13ed5 Binary files /dev/null and b/backend/uploads/images/3fede355-6aef-447b-ad40-5178adfe07d0.webp differ diff --git a/backend/uploads/images/4004bb6d-6263-4817-a2f8-4e61573a6707.webp b/backend/uploads/images/4004bb6d-6263-4817-a2f8-4e61573a6707.webp new file mode 100644 index 0000000..729438e Binary files /dev/null and b/backend/uploads/images/4004bb6d-6263-4817-a2f8-4e61573a6707.webp differ diff --git a/backend/uploads/images/400722d1-d524-4689-b611-706e059218c8.webp b/backend/uploads/images/400722d1-d524-4689-b611-706e059218c8.webp new file mode 100644 index 0000000..08ced3d Binary files /dev/null and b/backend/uploads/images/400722d1-d524-4689-b611-706e059218c8.webp differ diff --git a/backend/uploads/images/403eb760-87f4-44a0-b507-3fc0ecefe416.webp b/backend/uploads/images/403eb760-87f4-44a0-b507-3fc0ecefe416.webp new file mode 100644 index 0000000..0de6a5d Binary files /dev/null and b/backend/uploads/images/403eb760-87f4-44a0-b507-3fc0ecefe416.webp differ diff --git a/backend/uploads/images/405306fe-450b-4328-9182-8283b46f474e.webp b/backend/uploads/images/405306fe-450b-4328-9182-8283b46f474e.webp new file mode 100644 index 0000000..b26e490 Binary files /dev/null and b/backend/uploads/images/405306fe-450b-4328-9182-8283b46f474e.webp differ diff --git a/backend/uploads/images/40c2e915-1cd2-4ef1-920e-82a234fb3b15.webp b/backend/uploads/images/40c2e915-1cd2-4ef1-920e-82a234fb3b15.webp new file mode 100644 index 0000000..eed3d2b Binary files /dev/null and b/backend/uploads/images/40c2e915-1cd2-4ef1-920e-82a234fb3b15.webp differ diff --git a/backend/uploads/images/40d5ffa5-85ef-48a3-aacd-e511a1e50d79.webp b/backend/uploads/images/40d5ffa5-85ef-48a3-aacd-e511a1e50d79.webp new file mode 100644 index 0000000..1542b5c Binary files /dev/null and b/backend/uploads/images/40d5ffa5-85ef-48a3-aacd-e511a1e50d79.webp differ diff --git a/backend/uploads/images/40ecb57a-f6f5-4791-a7e7-c339cedab123.webp b/backend/uploads/images/40ecb57a-f6f5-4791-a7e7-c339cedab123.webp new file mode 100644 index 0000000..6864ab8 Binary files /dev/null and b/backend/uploads/images/40ecb57a-f6f5-4791-a7e7-c339cedab123.webp differ diff --git a/backend/uploads/images/4173a118-7dae-4897-9155-9893c1e9225e.webp b/backend/uploads/images/4173a118-7dae-4897-9155-9893c1e9225e.webp new file mode 100644 index 0000000..abab1d6 Binary files /dev/null and b/backend/uploads/images/4173a118-7dae-4897-9155-9893c1e9225e.webp differ diff --git a/backend/uploads/images/4196ab55-7347-4070-9cd6-a647f83e1c21.webp b/backend/uploads/images/4196ab55-7347-4070-9cd6-a647f83e1c21.webp new file mode 100644 index 0000000..d9c3bad Binary files /dev/null and b/backend/uploads/images/4196ab55-7347-4070-9cd6-a647f83e1c21.webp differ diff --git a/backend/uploads/images/419b60d5-dace-40ca-a8f7-dcbc3772aced.webp b/backend/uploads/images/419b60d5-dace-40ca-a8f7-dcbc3772aced.webp new file mode 100644 index 0000000..a300584 Binary files /dev/null and b/backend/uploads/images/419b60d5-dace-40ca-a8f7-dcbc3772aced.webp differ diff --git a/backend/uploads/images/420857e4-316e-4cfa-a43e-030c5d66b388.webp b/backend/uploads/images/420857e4-316e-4cfa-a43e-030c5d66b388.webp new file mode 100644 index 0000000..fb71871 Binary files /dev/null and b/backend/uploads/images/420857e4-316e-4cfa-a43e-030c5d66b388.webp differ diff --git a/backend/uploads/images/429168e4-36ee-4108-8497-18c4eedd3bfc.webp b/backend/uploads/images/429168e4-36ee-4108-8497-18c4eedd3bfc.webp new file mode 100644 index 0000000..a584704 Binary files /dev/null and b/backend/uploads/images/429168e4-36ee-4108-8497-18c4eedd3bfc.webp differ diff --git a/backend/uploads/images/433a11ad-82e5-41c6-82e7-114520573908.webp b/backend/uploads/images/433a11ad-82e5-41c6-82e7-114520573908.webp new file mode 100644 index 0000000..e58aa41 Binary files /dev/null and b/backend/uploads/images/433a11ad-82e5-41c6-82e7-114520573908.webp differ diff --git a/backend/uploads/images/4349a082-8251-420c-b4c2-90656faa3d48.webp b/backend/uploads/images/4349a082-8251-420c-b4c2-90656faa3d48.webp new file mode 100644 index 0000000..2268ce4 Binary files /dev/null and b/backend/uploads/images/4349a082-8251-420c-b4c2-90656faa3d48.webp differ diff --git a/backend/uploads/images/44266226-d051-4b35-bc02-0bc7bebc9341.webp b/backend/uploads/images/44266226-d051-4b35-bc02-0bc7bebc9341.webp new file mode 100644 index 0000000..edb978d Binary files /dev/null and b/backend/uploads/images/44266226-d051-4b35-bc02-0bc7bebc9341.webp differ diff --git a/backend/uploads/images/443af7a6-96b6-4207-a23d-34c8b795492d.webp b/backend/uploads/images/443af7a6-96b6-4207-a23d-34c8b795492d.webp new file mode 100644 index 0000000..5295b6c Binary files /dev/null and b/backend/uploads/images/443af7a6-96b6-4207-a23d-34c8b795492d.webp differ diff --git a/backend/uploads/images/4470b287-470b-4e34-a7f2-2457b17e3c4c.webp b/backend/uploads/images/4470b287-470b-4e34-a7f2-2457b17e3c4c.webp new file mode 100644 index 0000000..be86d91 Binary files /dev/null and b/backend/uploads/images/4470b287-470b-4e34-a7f2-2457b17e3c4c.webp differ diff --git a/backend/uploads/images/448c11b0-3881-4aef-9e6b-fe3a6a143d22.webp b/backend/uploads/images/448c11b0-3881-4aef-9e6b-fe3a6a143d22.webp new file mode 100644 index 0000000..9ec9e79 Binary files /dev/null and b/backend/uploads/images/448c11b0-3881-4aef-9e6b-fe3a6a143d22.webp differ diff --git a/backend/uploads/images/4503f889-a3e1-41dc-88d6-b692f5e63bb0.webp b/backend/uploads/images/4503f889-a3e1-41dc-88d6-b692f5e63bb0.webp new file mode 100644 index 0000000..0f771d0 Binary files /dev/null and b/backend/uploads/images/4503f889-a3e1-41dc-88d6-b692f5e63bb0.webp differ diff --git a/backend/uploads/images/4517be5a-a4e6-431b-94a5-1eb7475740f1.webp b/backend/uploads/images/4517be5a-a4e6-431b-94a5-1eb7475740f1.webp new file mode 100644 index 0000000..b2cbc5b Binary files /dev/null and b/backend/uploads/images/4517be5a-a4e6-431b-94a5-1eb7475740f1.webp differ diff --git a/backend/uploads/images/45465985-9934-4f2e-b3bf-5b9aa9fc1b2c.webp b/backend/uploads/images/45465985-9934-4f2e-b3bf-5b9aa9fc1b2c.webp new file mode 100644 index 0000000..38abe6b Binary files /dev/null and b/backend/uploads/images/45465985-9934-4f2e-b3bf-5b9aa9fc1b2c.webp differ diff --git a/backend/uploads/images/45ff262d-c4f9-42f7-ad01-91e3d6857372.webp b/backend/uploads/images/45ff262d-c4f9-42f7-ad01-91e3d6857372.webp new file mode 100644 index 0000000..620d284 Binary files /dev/null and b/backend/uploads/images/45ff262d-c4f9-42f7-ad01-91e3d6857372.webp differ diff --git a/backend/uploads/images/4633ee84-c177-4bd7-8275-df451fb8a619.webp b/backend/uploads/images/4633ee84-c177-4bd7-8275-df451fb8a619.webp new file mode 100644 index 0000000..a224f73 Binary files /dev/null and b/backend/uploads/images/4633ee84-c177-4bd7-8275-df451fb8a619.webp differ diff --git a/backend/uploads/images/467c2c0f-f219-401f-b98a-05b93086ad8b.webp b/backend/uploads/images/467c2c0f-f219-401f-b98a-05b93086ad8b.webp new file mode 100644 index 0000000..1894149 Binary files /dev/null and b/backend/uploads/images/467c2c0f-f219-401f-b98a-05b93086ad8b.webp differ diff --git a/backend/uploads/images/46fa4e0f-97e9-42ef-98ba-8ff3fa70ffd8.webp b/backend/uploads/images/46fa4e0f-97e9-42ef-98ba-8ff3fa70ffd8.webp new file mode 100644 index 0000000..2f81a4f Binary files /dev/null and b/backend/uploads/images/46fa4e0f-97e9-42ef-98ba-8ff3fa70ffd8.webp differ diff --git a/backend/uploads/images/47da3553-cc77-47ed-abf3-609b85e4e3e0.webp b/backend/uploads/images/47da3553-cc77-47ed-abf3-609b85e4e3e0.webp new file mode 100644 index 0000000..b0b4276 Binary files /dev/null and b/backend/uploads/images/47da3553-cc77-47ed-abf3-609b85e4e3e0.webp differ diff --git a/backend/uploads/images/481b564f-5a2c-4319-b62e-e45c02332629.webp b/backend/uploads/images/481b564f-5a2c-4319-b62e-e45c02332629.webp new file mode 100644 index 0000000..15126de Binary files /dev/null and b/backend/uploads/images/481b564f-5a2c-4319-b62e-e45c02332629.webp differ diff --git a/backend/uploads/images/4856611c-d395-4c9a-9dae-bb8730bb4b41.webp b/backend/uploads/images/4856611c-d395-4c9a-9dae-bb8730bb4b41.webp new file mode 100644 index 0000000..ca86483 Binary files /dev/null and b/backend/uploads/images/4856611c-d395-4c9a-9dae-bb8730bb4b41.webp differ diff --git a/backend/uploads/images/48864d72-fdd6-4efc-b710-a25611dfb198.webp b/backend/uploads/images/48864d72-fdd6-4efc-b710-a25611dfb198.webp new file mode 100644 index 0000000..0cd8c8a Binary files /dev/null and b/backend/uploads/images/48864d72-fdd6-4efc-b710-a25611dfb198.webp differ diff --git a/backend/uploads/images/48ae1799-d22a-4b29-bf66-f107000ffd89.webp b/backend/uploads/images/48ae1799-d22a-4b29-bf66-f107000ffd89.webp new file mode 100644 index 0000000..852461e Binary files /dev/null and b/backend/uploads/images/48ae1799-d22a-4b29-bf66-f107000ffd89.webp differ diff --git a/backend/uploads/images/48c15612-40b0-4113-9083-7205f7e7d218.webp b/backend/uploads/images/48c15612-40b0-4113-9083-7205f7e7d218.webp new file mode 100644 index 0000000..f4a68a7 Binary files /dev/null and b/backend/uploads/images/48c15612-40b0-4113-9083-7205f7e7d218.webp differ diff --git a/backend/uploads/images/48d89384-264a-4c02-93c0-40550b6d1c97.webp b/backend/uploads/images/48d89384-264a-4c02-93c0-40550b6d1c97.webp new file mode 100644 index 0000000..c1ae36d Binary files /dev/null and b/backend/uploads/images/48d89384-264a-4c02-93c0-40550b6d1c97.webp differ diff --git a/backend/uploads/images/4926b350-b11d-4b86-9657-6d3a116a4a9b.webp b/backend/uploads/images/4926b350-b11d-4b86-9657-6d3a116a4a9b.webp new file mode 100644 index 0000000..fe506ad Binary files /dev/null and b/backend/uploads/images/4926b350-b11d-4b86-9657-6d3a116a4a9b.webp differ diff --git a/backend/uploads/images/496cb0e1-2fee-471b-8827-4de919f6e406.webp b/backend/uploads/images/496cb0e1-2fee-471b-8827-4de919f6e406.webp new file mode 100644 index 0000000..7ff6443 Binary files /dev/null and b/backend/uploads/images/496cb0e1-2fee-471b-8827-4de919f6e406.webp differ diff --git a/backend/uploads/images/4982ea62-21ed-4164-b438-b4ab5750db46.webp b/backend/uploads/images/4982ea62-21ed-4164-b438-b4ab5750db46.webp new file mode 100644 index 0000000..e5aa9e3 Binary files /dev/null and b/backend/uploads/images/4982ea62-21ed-4164-b438-b4ab5750db46.webp differ diff --git a/backend/uploads/images/4a184732-177f-46d4-983d-0e28776a636b.webp b/backend/uploads/images/4a184732-177f-46d4-983d-0e28776a636b.webp new file mode 100644 index 0000000..9bba807 Binary files /dev/null and b/backend/uploads/images/4a184732-177f-46d4-983d-0e28776a636b.webp differ diff --git a/backend/uploads/images/4a282e65-cba0-4f24-a245-17e777266b9b.webp b/backend/uploads/images/4a282e65-cba0-4f24-a245-17e777266b9b.webp new file mode 100644 index 0000000..a053ed5 Binary files /dev/null and b/backend/uploads/images/4a282e65-cba0-4f24-a245-17e777266b9b.webp differ diff --git a/backend/uploads/images/4a52e9f7-26d7-4c73-b8c7-3740f0b1c8a2.webp b/backend/uploads/images/4a52e9f7-26d7-4c73-b8c7-3740f0b1c8a2.webp new file mode 100644 index 0000000..824958c Binary files /dev/null and b/backend/uploads/images/4a52e9f7-26d7-4c73-b8c7-3740f0b1c8a2.webp differ diff --git a/backend/uploads/images/4ac3acf3-b567-4be9-af3a-c26ba554d8cf.webp b/backend/uploads/images/4ac3acf3-b567-4be9-af3a-c26ba554d8cf.webp new file mode 100644 index 0000000..42dc30d Binary files /dev/null and b/backend/uploads/images/4ac3acf3-b567-4be9-af3a-c26ba554d8cf.webp differ diff --git a/backend/uploads/images/4af449ee-ab7c-4e3d-ac57-1025b8e2deb0.webp b/backend/uploads/images/4af449ee-ab7c-4e3d-ac57-1025b8e2deb0.webp new file mode 100644 index 0000000..257202d Binary files /dev/null and b/backend/uploads/images/4af449ee-ab7c-4e3d-ac57-1025b8e2deb0.webp differ diff --git a/backend/uploads/images/4b242a25-9f9a-47fc-b398-b1c5d8ab9de7.webp b/backend/uploads/images/4b242a25-9f9a-47fc-b398-b1c5d8ab9de7.webp new file mode 100644 index 0000000..47fbb9b Binary files /dev/null and b/backend/uploads/images/4b242a25-9f9a-47fc-b398-b1c5d8ab9de7.webp differ diff --git a/backend/uploads/images/4b2f8209-6e2f-4376-a5a0-7318fd0f16dc.webp b/backend/uploads/images/4b2f8209-6e2f-4376-a5a0-7318fd0f16dc.webp new file mode 100644 index 0000000..a6ab680 Binary files /dev/null and b/backend/uploads/images/4b2f8209-6e2f-4376-a5a0-7318fd0f16dc.webp differ diff --git a/backend/uploads/images/4b9eff0c-37c5-4b96-b38c-af884bdaec1b.webp b/backend/uploads/images/4b9eff0c-37c5-4b96-b38c-af884bdaec1b.webp new file mode 100644 index 0000000..cbc7ad6 Binary files /dev/null and b/backend/uploads/images/4b9eff0c-37c5-4b96-b38c-af884bdaec1b.webp differ diff --git a/backend/uploads/images/4c154ef2-43ad-4402-b5a9-314919a16b9b.webp b/backend/uploads/images/4c154ef2-43ad-4402-b5a9-314919a16b9b.webp new file mode 100644 index 0000000..8902b90 Binary files /dev/null and b/backend/uploads/images/4c154ef2-43ad-4402-b5a9-314919a16b9b.webp differ diff --git a/backend/uploads/images/4c2140a0-7e1a-45cd-92ad-4136bb92aaa1.webp b/backend/uploads/images/4c2140a0-7e1a-45cd-92ad-4136bb92aaa1.webp new file mode 100644 index 0000000..8009631 Binary files /dev/null and b/backend/uploads/images/4c2140a0-7e1a-45cd-92ad-4136bb92aaa1.webp differ diff --git a/backend/uploads/images/4c76c17f-0ce5-4c21-a5e2-2addb3438f68.webp b/backend/uploads/images/4c76c17f-0ce5-4c21-a5e2-2addb3438f68.webp new file mode 100644 index 0000000..61eee7f Binary files /dev/null and b/backend/uploads/images/4c76c17f-0ce5-4c21-a5e2-2addb3438f68.webp differ diff --git a/backend/uploads/images/4d4831c0-8508-489d-ba3a-4e5f4e249ef7.webp b/backend/uploads/images/4d4831c0-8508-489d-ba3a-4e5f4e249ef7.webp new file mode 100644 index 0000000..9c4d8c8 Binary files /dev/null and b/backend/uploads/images/4d4831c0-8508-489d-ba3a-4e5f4e249ef7.webp differ diff --git a/backend/uploads/images/4d8cd8ba-4b80-4db2-bd50-449c09d925fe.webp b/backend/uploads/images/4d8cd8ba-4b80-4db2-bd50-449c09d925fe.webp new file mode 100644 index 0000000..0431135 Binary files /dev/null and b/backend/uploads/images/4d8cd8ba-4b80-4db2-bd50-449c09d925fe.webp differ diff --git a/backend/uploads/images/4dc7e77c-7847-40ea-9260-02bf75f11930.webp b/backend/uploads/images/4dc7e77c-7847-40ea-9260-02bf75f11930.webp new file mode 100644 index 0000000..96da59b Binary files /dev/null and b/backend/uploads/images/4dc7e77c-7847-40ea-9260-02bf75f11930.webp differ diff --git a/backend/uploads/images/4e1f489a-566c-420f-8d39-ffed92b1b50e.webp b/backend/uploads/images/4e1f489a-566c-420f-8d39-ffed92b1b50e.webp new file mode 100644 index 0000000..c1bb79b Binary files /dev/null and b/backend/uploads/images/4e1f489a-566c-420f-8d39-ffed92b1b50e.webp differ diff --git a/backend/uploads/images/4e3e7451-aa39-470f-b9e1-e4d2d5c3506a.webp b/backend/uploads/images/4e3e7451-aa39-470f-b9e1-e4d2d5c3506a.webp new file mode 100644 index 0000000..69a995a Binary files /dev/null and b/backend/uploads/images/4e3e7451-aa39-470f-b9e1-e4d2d5c3506a.webp differ diff --git a/backend/uploads/images/4ea55b1d-4fbd-4918-a74e-e2aea190bcb6.webp b/backend/uploads/images/4ea55b1d-4fbd-4918-a74e-e2aea190bcb6.webp new file mode 100644 index 0000000..91be0ae Binary files /dev/null and b/backend/uploads/images/4ea55b1d-4fbd-4918-a74e-e2aea190bcb6.webp differ diff --git a/backend/uploads/images/4f0510b9-0760-4262-9327-02d6e53360e4.webp b/backend/uploads/images/4f0510b9-0760-4262-9327-02d6e53360e4.webp new file mode 100644 index 0000000..b15c92b Binary files /dev/null and b/backend/uploads/images/4f0510b9-0760-4262-9327-02d6e53360e4.webp differ diff --git a/backend/uploads/images/4f5347f3-1742-4a30-bc02-620221db5558.webp b/backend/uploads/images/4f5347f3-1742-4a30-bc02-620221db5558.webp new file mode 100644 index 0000000..48a2d16 Binary files /dev/null and b/backend/uploads/images/4f5347f3-1742-4a30-bc02-620221db5558.webp differ diff --git a/backend/uploads/images/4f553abc-256a-4168-b35d-bf43ec65ba3b.webp b/backend/uploads/images/4f553abc-256a-4168-b35d-bf43ec65ba3b.webp new file mode 100644 index 0000000..6587020 Binary files /dev/null and b/backend/uploads/images/4f553abc-256a-4168-b35d-bf43ec65ba3b.webp differ diff --git a/backend/uploads/images/4ff65cbd-7eed-4c7e-b0ea-35cb79fd6776.webp b/backend/uploads/images/4ff65cbd-7eed-4c7e-b0ea-35cb79fd6776.webp new file mode 100644 index 0000000..aa6889a Binary files /dev/null and b/backend/uploads/images/4ff65cbd-7eed-4c7e-b0ea-35cb79fd6776.webp differ diff --git a/backend/uploads/images/5012f934-7189-4f4b-a2cb-becc91cb6024.webp b/backend/uploads/images/5012f934-7189-4f4b-a2cb-becc91cb6024.webp new file mode 100644 index 0000000..257e5c7 Binary files /dev/null and b/backend/uploads/images/5012f934-7189-4f4b-a2cb-becc91cb6024.webp differ diff --git a/backend/uploads/images/506cc169-4892-4dd9-b026-89226a039a98.webp b/backend/uploads/images/506cc169-4892-4dd9-b026-89226a039a98.webp new file mode 100644 index 0000000..a4e7b89 Binary files /dev/null and b/backend/uploads/images/506cc169-4892-4dd9-b026-89226a039a98.webp differ diff --git a/backend/uploads/images/51268867-2dff-4c22-8f59-7ffbe37631f3.webp b/backend/uploads/images/51268867-2dff-4c22-8f59-7ffbe37631f3.webp new file mode 100644 index 0000000..e2d7249 Binary files /dev/null and b/backend/uploads/images/51268867-2dff-4c22-8f59-7ffbe37631f3.webp differ diff --git a/backend/uploads/images/5128bef6-9278-4f20-be1a-be11a0a896e5.webp b/backend/uploads/images/5128bef6-9278-4f20-be1a-be11a0a896e5.webp new file mode 100644 index 0000000..599b229 Binary files /dev/null and b/backend/uploads/images/5128bef6-9278-4f20-be1a-be11a0a896e5.webp differ diff --git a/backend/uploads/images/51600668-7bac-443e-b0c8-b315d64c30dd.webp b/backend/uploads/images/51600668-7bac-443e-b0c8-b315d64c30dd.webp new file mode 100644 index 0000000..fb6fe53 Binary files /dev/null and b/backend/uploads/images/51600668-7bac-443e-b0c8-b315d64c30dd.webp differ diff --git a/backend/uploads/images/51953cce-5a41-43ac-940a-becb261e7890.webp b/backend/uploads/images/51953cce-5a41-43ac-940a-becb261e7890.webp new file mode 100644 index 0000000..01955d1 Binary files /dev/null and b/backend/uploads/images/51953cce-5a41-43ac-940a-becb261e7890.webp differ diff --git a/backend/uploads/images/523ed6d7-71eb-4726-8fa7-27ca94621dae.webp b/backend/uploads/images/523ed6d7-71eb-4726-8fa7-27ca94621dae.webp new file mode 100644 index 0000000..06d654c Binary files /dev/null and b/backend/uploads/images/523ed6d7-71eb-4726-8fa7-27ca94621dae.webp differ diff --git a/backend/uploads/images/526130c6-f4e7-4171-b666-dc1a48a1566d.webp b/backend/uploads/images/526130c6-f4e7-4171-b666-dc1a48a1566d.webp new file mode 100644 index 0000000..d0536d3 Binary files /dev/null and b/backend/uploads/images/526130c6-f4e7-4171-b666-dc1a48a1566d.webp differ diff --git a/backend/uploads/images/5262e33e-876d-4c67-809c-0addc1e399ad.webp b/backend/uploads/images/5262e33e-876d-4c67-809c-0addc1e399ad.webp new file mode 100644 index 0000000..a4f66b8 Binary files /dev/null and b/backend/uploads/images/5262e33e-876d-4c67-809c-0addc1e399ad.webp differ diff --git a/backend/uploads/images/5273077d-9101-4cb2-9b3d-85c2ef239898.webp b/backend/uploads/images/5273077d-9101-4cb2-9b3d-85c2ef239898.webp new file mode 100644 index 0000000..27453f5 Binary files /dev/null and b/backend/uploads/images/5273077d-9101-4cb2-9b3d-85c2ef239898.webp differ diff --git a/backend/uploads/images/5275d4ac-4a41-42b4-9ebc-b02024450682.webp b/backend/uploads/images/5275d4ac-4a41-42b4-9ebc-b02024450682.webp new file mode 100644 index 0000000..c38b32b Binary files /dev/null and b/backend/uploads/images/5275d4ac-4a41-42b4-9ebc-b02024450682.webp differ diff --git a/backend/uploads/images/52bdb323-df08-489f-b261-721d271f786c.webp b/backend/uploads/images/52bdb323-df08-489f-b261-721d271f786c.webp new file mode 100644 index 0000000..8a9ce4d Binary files /dev/null and b/backend/uploads/images/52bdb323-df08-489f-b261-721d271f786c.webp differ diff --git a/backend/uploads/images/52ef4a8d-adbd-4f73-8811-27847d6d5ab9.webp b/backend/uploads/images/52ef4a8d-adbd-4f73-8811-27847d6d5ab9.webp new file mode 100644 index 0000000..7d769cf Binary files /dev/null and b/backend/uploads/images/52ef4a8d-adbd-4f73-8811-27847d6d5ab9.webp differ diff --git a/backend/uploads/images/53e2ee02-e7bc-4207-926e-9873cf0d901b.webp b/backend/uploads/images/53e2ee02-e7bc-4207-926e-9873cf0d901b.webp new file mode 100644 index 0000000..e44d8d1 Binary files /dev/null and b/backend/uploads/images/53e2ee02-e7bc-4207-926e-9873cf0d901b.webp differ diff --git a/backend/uploads/images/549b4468-ee3a-4077-b8b2-ade411efc103.webp b/backend/uploads/images/549b4468-ee3a-4077-b8b2-ade411efc103.webp new file mode 100644 index 0000000..5356067 Binary files /dev/null and b/backend/uploads/images/549b4468-ee3a-4077-b8b2-ade411efc103.webp differ diff --git a/backend/uploads/images/54dea095-237f-4a39-b605-f52f398c6f54.webp b/backend/uploads/images/54dea095-237f-4a39-b605-f52f398c6f54.webp new file mode 100644 index 0000000..8ebb89a Binary files /dev/null and b/backend/uploads/images/54dea095-237f-4a39-b605-f52f398c6f54.webp differ diff --git a/backend/uploads/images/55926067-421d-49f9-97b9-97cc19ee1c95.webp b/backend/uploads/images/55926067-421d-49f9-97b9-97cc19ee1c95.webp new file mode 100644 index 0000000..f8ac5be Binary files /dev/null and b/backend/uploads/images/55926067-421d-49f9-97b9-97cc19ee1c95.webp differ diff --git a/backend/uploads/images/55adbacf-f841-46b1-8a93-2f4abd531d7f.webp b/backend/uploads/images/55adbacf-f841-46b1-8a93-2f4abd531d7f.webp new file mode 100644 index 0000000..ed544de Binary files /dev/null and b/backend/uploads/images/55adbacf-f841-46b1-8a93-2f4abd531d7f.webp differ diff --git a/backend/uploads/images/55ebb85b-d7f4-403c-bb4d-8db273d5fa0a.webp b/backend/uploads/images/55ebb85b-d7f4-403c-bb4d-8db273d5fa0a.webp new file mode 100644 index 0000000..c82418a Binary files /dev/null and b/backend/uploads/images/55ebb85b-d7f4-403c-bb4d-8db273d5fa0a.webp differ diff --git a/backend/uploads/images/564fb034-93af-40da-940b-555a88192fc1.webp b/backend/uploads/images/564fb034-93af-40da-940b-555a88192fc1.webp new file mode 100644 index 0000000..859c4ff Binary files /dev/null and b/backend/uploads/images/564fb034-93af-40da-940b-555a88192fc1.webp differ diff --git a/backend/uploads/images/5652dee1-b0d0-4cea-8d57-0760c520086d.webp b/backend/uploads/images/5652dee1-b0d0-4cea-8d57-0760c520086d.webp new file mode 100644 index 0000000..194b896 Binary files /dev/null and b/backend/uploads/images/5652dee1-b0d0-4cea-8d57-0760c520086d.webp differ diff --git a/backend/uploads/images/567a661f-d1fb-49c4-883b-c8422d970e92.webp b/backend/uploads/images/567a661f-d1fb-49c4-883b-c8422d970e92.webp new file mode 100644 index 0000000..cb73c58 Binary files /dev/null and b/backend/uploads/images/567a661f-d1fb-49c4-883b-c8422d970e92.webp differ diff --git a/backend/uploads/images/56bf2747-c6e1-4f56-a272-076ec6294277.webp b/backend/uploads/images/56bf2747-c6e1-4f56-a272-076ec6294277.webp new file mode 100644 index 0000000..bb63b26 Binary files /dev/null and b/backend/uploads/images/56bf2747-c6e1-4f56-a272-076ec6294277.webp differ diff --git a/backend/uploads/images/56d74458-d85c-4619-b639-2beb62ebdbe1.webp b/backend/uploads/images/56d74458-d85c-4619-b639-2beb62ebdbe1.webp new file mode 100644 index 0000000..c856b61 Binary files /dev/null and b/backend/uploads/images/56d74458-d85c-4619-b639-2beb62ebdbe1.webp differ diff --git a/backend/uploads/images/56e98177-6270-4542-aa12-57b5f809c355.webp b/backend/uploads/images/56e98177-6270-4542-aa12-57b5f809c355.webp new file mode 100644 index 0000000..7a76d32 Binary files /dev/null and b/backend/uploads/images/56e98177-6270-4542-aa12-57b5f809c355.webp differ diff --git a/backend/uploads/images/570fd460-eda6-4b4c-933f-f41e1bde7b67.webp b/backend/uploads/images/570fd460-eda6-4b4c-933f-f41e1bde7b67.webp new file mode 100644 index 0000000..4a87265 Binary files /dev/null and b/backend/uploads/images/570fd460-eda6-4b4c-933f-f41e1bde7b67.webp differ diff --git a/backend/uploads/images/5855675a-74a0-47e7-be20-aa634b4b3ae5.webp b/backend/uploads/images/5855675a-74a0-47e7-be20-aa634b4b3ae5.webp new file mode 100644 index 0000000..a791743 Binary files /dev/null and b/backend/uploads/images/5855675a-74a0-47e7-be20-aa634b4b3ae5.webp differ diff --git a/backend/uploads/images/587513ea-3681-4926-a437-d309ff07f4e3.webp b/backend/uploads/images/587513ea-3681-4926-a437-d309ff07f4e3.webp new file mode 100644 index 0000000..af2fec1 Binary files /dev/null and b/backend/uploads/images/587513ea-3681-4926-a437-d309ff07f4e3.webp differ diff --git a/backend/uploads/images/588d949b-ab1e-4426-b2ec-cad3f2189dd3.webp b/backend/uploads/images/588d949b-ab1e-4426-b2ec-cad3f2189dd3.webp new file mode 100644 index 0000000..2975e77 Binary files /dev/null and b/backend/uploads/images/588d949b-ab1e-4426-b2ec-cad3f2189dd3.webp differ diff --git a/backend/uploads/images/58a20d93-1f2f-4d8f-b671-1d5ed1aed90d.webp b/backend/uploads/images/58a20d93-1f2f-4d8f-b671-1d5ed1aed90d.webp new file mode 100644 index 0000000..526776a Binary files /dev/null and b/backend/uploads/images/58a20d93-1f2f-4d8f-b671-1d5ed1aed90d.webp differ diff --git a/backend/uploads/images/5905ed5e-aba4-4f41-8854-1b4f3c9204c3.webp b/backend/uploads/images/5905ed5e-aba4-4f41-8854-1b4f3c9204c3.webp new file mode 100644 index 0000000..8c866d2 Binary files /dev/null and b/backend/uploads/images/5905ed5e-aba4-4f41-8854-1b4f3c9204c3.webp differ diff --git a/backend/uploads/images/59309eaf-27e6-4bab-95d2-09544def6771.webp b/backend/uploads/images/59309eaf-27e6-4bab-95d2-09544def6771.webp new file mode 100644 index 0000000..8d45f0e Binary files /dev/null and b/backend/uploads/images/59309eaf-27e6-4bab-95d2-09544def6771.webp differ diff --git a/backend/uploads/images/59521f2b-fe60-4184-80a6-600d34095c33.webp b/backend/uploads/images/59521f2b-fe60-4184-80a6-600d34095c33.webp new file mode 100644 index 0000000..3426988 Binary files /dev/null and b/backend/uploads/images/59521f2b-fe60-4184-80a6-600d34095c33.webp differ diff --git a/backend/uploads/images/59a256ec-b00c-4274-a825-b1e8e852a65e.webp b/backend/uploads/images/59a256ec-b00c-4274-a825-b1e8e852a65e.webp new file mode 100644 index 0000000..f14f1df Binary files /dev/null and b/backend/uploads/images/59a256ec-b00c-4274-a825-b1e8e852a65e.webp differ diff --git a/backend/uploads/images/5a1202bf-5c00-48d8-be4a-8a14988b8ff7.webp b/backend/uploads/images/5a1202bf-5c00-48d8-be4a-8a14988b8ff7.webp new file mode 100644 index 0000000..ca6f14c Binary files /dev/null and b/backend/uploads/images/5a1202bf-5c00-48d8-be4a-8a14988b8ff7.webp differ diff --git a/backend/uploads/images/5a42f4cf-5a68-4c4d-bb92-88c81b27d509.webp b/backend/uploads/images/5a42f4cf-5a68-4c4d-bb92-88c81b27d509.webp new file mode 100644 index 0000000..d5526a3 Binary files /dev/null and b/backend/uploads/images/5a42f4cf-5a68-4c4d-bb92-88c81b27d509.webp differ diff --git a/backend/uploads/images/5aafc678-4a46-45ff-b971-a75d0cbb76fc.webp b/backend/uploads/images/5aafc678-4a46-45ff-b971-a75d0cbb76fc.webp new file mode 100644 index 0000000..7f32acb Binary files /dev/null and b/backend/uploads/images/5aafc678-4a46-45ff-b971-a75d0cbb76fc.webp differ diff --git a/backend/uploads/images/5ad0d43b-eb29-4557-8ca5-80eb5f2f59af.webp b/backend/uploads/images/5ad0d43b-eb29-4557-8ca5-80eb5f2f59af.webp new file mode 100644 index 0000000..68c54ea Binary files /dev/null and b/backend/uploads/images/5ad0d43b-eb29-4557-8ca5-80eb5f2f59af.webp differ diff --git a/backend/uploads/images/5aeb9301-b96e-48d2-917f-1034537966a4.webp b/backend/uploads/images/5aeb9301-b96e-48d2-917f-1034537966a4.webp new file mode 100644 index 0000000..91e638d Binary files /dev/null and b/backend/uploads/images/5aeb9301-b96e-48d2-917f-1034537966a4.webp differ diff --git a/backend/uploads/images/5b52bafc-d5e1-437e-bbc5-c4e4acaa9370.webp b/backend/uploads/images/5b52bafc-d5e1-437e-bbc5-c4e4acaa9370.webp new file mode 100644 index 0000000..cbf60d7 Binary files /dev/null and b/backend/uploads/images/5b52bafc-d5e1-437e-bbc5-c4e4acaa9370.webp differ diff --git a/backend/uploads/images/5c8547a1-e2c8-49bb-9a1a-fccc381ce007.webp b/backend/uploads/images/5c8547a1-e2c8-49bb-9a1a-fccc381ce007.webp new file mode 100644 index 0000000..1408ee1 Binary files /dev/null and b/backend/uploads/images/5c8547a1-e2c8-49bb-9a1a-fccc381ce007.webp differ diff --git a/backend/uploads/images/5c9b3032-ea58-4a20-93ed-af2ea7e784b0.webp b/backend/uploads/images/5c9b3032-ea58-4a20-93ed-af2ea7e784b0.webp new file mode 100644 index 0000000..fda990e Binary files /dev/null and b/backend/uploads/images/5c9b3032-ea58-4a20-93ed-af2ea7e784b0.webp differ diff --git a/backend/uploads/images/5d3a47de-f5b2-4282-b50c-09ce4161468f.webp b/backend/uploads/images/5d3a47de-f5b2-4282-b50c-09ce4161468f.webp new file mode 100644 index 0000000..f8c492e Binary files /dev/null and b/backend/uploads/images/5d3a47de-f5b2-4282-b50c-09ce4161468f.webp differ diff --git a/backend/uploads/images/5d53fb4c-4fdd-4f73-a49d-3c57302927a9.webp b/backend/uploads/images/5d53fb4c-4fdd-4f73-a49d-3c57302927a9.webp new file mode 100644 index 0000000..bad0ff8 Binary files /dev/null and b/backend/uploads/images/5d53fb4c-4fdd-4f73-a49d-3c57302927a9.webp differ diff --git a/backend/uploads/images/5e1b5ab5-54c7-4688-a726-7199a38a438d.webp b/backend/uploads/images/5e1b5ab5-54c7-4688-a726-7199a38a438d.webp new file mode 100644 index 0000000..efcdc54 Binary files /dev/null and b/backend/uploads/images/5e1b5ab5-54c7-4688-a726-7199a38a438d.webp differ diff --git a/backend/uploads/images/5e5bfeb6-fe57-4e02-b416-51bd128f7f68.webp b/backend/uploads/images/5e5bfeb6-fe57-4e02-b416-51bd128f7f68.webp new file mode 100644 index 0000000..9aeb362 Binary files /dev/null and b/backend/uploads/images/5e5bfeb6-fe57-4e02-b416-51bd128f7f68.webp differ diff --git a/backend/uploads/images/5fb50ca9-af68-4b8c-9b23-f4307f4d38a8.webp b/backend/uploads/images/5fb50ca9-af68-4b8c-9b23-f4307f4d38a8.webp new file mode 100644 index 0000000..4adec5f Binary files /dev/null and b/backend/uploads/images/5fb50ca9-af68-4b8c-9b23-f4307f4d38a8.webp differ diff --git a/backend/uploads/images/5fdf0e37-e066-4cdb-bc48-2e85b473cf05.webp b/backend/uploads/images/5fdf0e37-e066-4cdb-bc48-2e85b473cf05.webp new file mode 100644 index 0000000..9d0a141 Binary files /dev/null and b/backend/uploads/images/5fdf0e37-e066-4cdb-bc48-2e85b473cf05.webp differ diff --git a/backend/uploads/images/6086d577-3e23-47b5-9b17-55f3c40749f7.webp b/backend/uploads/images/6086d577-3e23-47b5-9b17-55f3c40749f7.webp new file mode 100644 index 0000000..da04f74 Binary files /dev/null and b/backend/uploads/images/6086d577-3e23-47b5-9b17-55f3c40749f7.webp differ diff --git a/backend/uploads/images/610fb1c8-d30f-4b88-b731-7da7cdd81650.webp b/backend/uploads/images/610fb1c8-d30f-4b88-b731-7da7cdd81650.webp new file mode 100644 index 0000000..4226233 Binary files /dev/null and b/backend/uploads/images/610fb1c8-d30f-4b88-b731-7da7cdd81650.webp differ diff --git a/backend/uploads/images/6131fb41-ff51-448c-928b-a0edc4a4289e.webp b/backend/uploads/images/6131fb41-ff51-448c-928b-a0edc4a4289e.webp new file mode 100644 index 0000000..5c0cb33 Binary files /dev/null and b/backend/uploads/images/6131fb41-ff51-448c-928b-a0edc4a4289e.webp differ diff --git a/backend/uploads/images/6137420f-669c-491c-8bd2-5c7ea8f6415c.webp b/backend/uploads/images/6137420f-669c-491c-8bd2-5c7ea8f6415c.webp new file mode 100644 index 0000000..413b6fa Binary files /dev/null and b/backend/uploads/images/6137420f-669c-491c-8bd2-5c7ea8f6415c.webp differ diff --git a/backend/uploads/images/61995116-2a31-415c-99ea-cbb77dbe7869.webp b/backend/uploads/images/61995116-2a31-415c-99ea-cbb77dbe7869.webp new file mode 100644 index 0000000..32d8745 Binary files /dev/null and b/backend/uploads/images/61995116-2a31-415c-99ea-cbb77dbe7869.webp differ diff --git a/backend/uploads/images/61be8977-6014-4796-9ca1-c7364ab4c8b6.webp b/backend/uploads/images/61be8977-6014-4796-9ca1-c7364ab4c8b6.webp new file mode 100644 index 0000000..62681f8 Binary files /dev/null and b/backend/uploads/images/61be8977-6014-4796-9ca1-c7364ab4c8b6.webp differ diff --git a/backend/uploads/images/61c68d79-8271-4803-a7ce-bf7ad187845b.webp b/backend/uploads/images/61c68d79-8271-4803-a7ce-bf7ad187845b.webp new file mode 100644 index 0000000..a2d78c0 Binary files /dev/null and b/backend/uploads/images/61c68d79-8271-4803-a7ce-bf7ad187845b.webp differ diff --git a/backend/uploads/images/62a84f0b-f722-4589-8572-b925ca994855.webp b/backend/uploads/images/62a84f0b-f722-4589-8572-b925ca994855.webp new file mode 100644 index 0000000..53ea310 Binary files /dev/null and b/backend/uploads/images/62a84f0b-f722-4589-8572-b925ca994855.webp differ diff --git a/backend/uploads/images/62aba716-fc53-4593-b6dd-d78dd92b07d1.webp b/backend/uploads/images/62aba716-fc53-4593-b6dd-d78dd92b07d1.webp new file mode 100644 index 0000000..f6bb4d1 Binary files /dev/null and b/backend/uploads/images/62aba716-fc53-4593-b6dd-d78dd92b07d1.webp differ diff --git a/backend/uploads/images/6313785c-594e-4980-9eca-61e77d4e58e5.webp b/backend/uploads/images/6313785c-594e-4980-9eca-61e77d4e58e5.webp new file mode 100644 index 0000000..bdba377 Binary files /dev/null and b/backend/uploads/images/6313785c-594e-4980-9eca-61e77d4e58e5.webp differ diff --git a/backend/uploads/images/633737c8-6e9e-4908-8dbe-64d246b44153.webp b/backend/uploads/images/633737c8-6e9e-4908-8dbe-64d246b44153.webp new file mode 100644 index 0000000..d8bbebf Binary files /dev/null and b/backend/uploads/images/633737c8-6e9e-4908-8dbe-64d246b44153.webp differ diff --git a/backend/uploads/images/634346a8-f6aa-4c61-b28f-774f84bcbf5b.webp b/backend/uploads/images/634346a8-f6aa-4c61-b28f-774f84bcbf5b.webp new file mode 100644 index 0000000..4b79d63 Binary files /dev/null and b/backend/uploads/images/634346a8-f6aa-4c61-b28f-774f84bcbf5b.webp differ diff --git a/backend/uploads/images/634c2a09-ba99-46f0-97a6-a311563706da.webp b/backend/uploads/images/634c2a09-ba99-46f0-97a6-a311563706da.webp new file mode 100644 index 0000000..6b77406 Binary files /dev/null and b/backend/uploads/images/634c2a09-ba99-46f0-97a6-a311563706da.webp differ diff --git a/backend/uploads/images/63fc3e7f-1697-4690-9049-6506d76680c7.webp b/backend/uploads/images/63fc3e7f-1697-4690-9049-6506d76680c7.webp new file mode 100644 index 0000000..339a789 Binary files /dev/null and b/backend/uploads/images/63fc3e7f-1697-4690-9049-6506d76680c7.webp differ diff --git a/backend/uploads/images/641beb2d-c163-4629-a0cc-e8c31c8b108a.webp b/backend/uploads/images/641beb2d-c163-4629-a0cc-e8c31c8b108a.webp new file mode 100644 index 0000000..8162f3d Binary files /dev/null and b/backend/uploads/images/641beb2d-c163-4629-a0cc-e8c31c8b108a.webp differ diff --git a/backend/uploads/images/656b3517-4e9a-4316-b53e-36911ce64de0.webp b/backend/uploads/images/656b3517-4e9a-4316-b53e-36911ce64de0.webp new file mode 100644 index 0000000..fa77f8d Binary files /dev/null and b/backend/uploads/images/656b3517-4e9a-4316-b53e-36911ce64de0.webp differ diff --git a/backend/uploads/images/65d6be17-7d44-4a9a-b2a4-5bb9a32295fd.webp b/backend/uploads/images/65d6be17-7d44-4a9a-b2a4-5bb9a32295fd.webp new file mode 100644 index 0000000..a68123d Binary files /dev/null and b/backend/uploads/images/65d6be17-7d44-4a9a-b2a4-5bb9a32295fd.webp differ diff --git a/backend/uploads/images/66a17123-5c98-46e9-9b52-e214f6692ae3.webp b/backend/uploads/images/66a17123-5c98-46e9-9b52-e214f6692ae3.webp new file mode 100644 index 0000000..b05b82e Binary files /dev/null and b/backend/uploads/images/66a17123-5c98-46e9-9b52-e214f6692ae3.webp differ diff --git a/backend/uploads/images/675e7194-8455-4ea7-9080-f2c6ffbc9417.webp b/backend/uploads/images/675e7194-8455-4ea7-9080-f2c6ffbc9417.webp new file mode 100644 index 0000000..d816d6a Binary files /dev/null and b/backend/uploads/images/675e7194-8455-4ea7-9080-f2c6ffbc9417.webp differ diff --git a/backend/uploads/images/6789cdd3-c680-4586-bb93-a3e353d0b7e5.webp b/backend/uploads/images/6789cdd3-c680-4586-bb93-a3e353d0b7e5.webp new file mode 100644 index 0000000..ae9b84a Binary files /dev/null and b/backend/uploads/images/6789cdd3-c680-4586-bb93-a3e353d0b7e5.webp differ diff --git a/backend/uploads/images/67ae441d-f7d7-4bc5-a28b-6c7f3f04eee8.webp b/backend/uploads/images/67ae441d-f7d7-4bc5-a28b-6c7f3f04eee8.webp new file mode 100644 index 0000000..af60d31 Binary files /dev/null and b/backend/uploads/images/67ae441d-f7d7-4bc5-a28b-6c7f3f04eee8.webp differ diff --git a/backend/uploads/images/67b9b488-ce87-4e12-9d95-76bd6eb45968.webp b/backend/uploads/images/67b9b488-ce87-4e12-9d95-76bd6eb45968.webp new file mode 100644 index 0000000..056011c Binary files /dev/null and b/backend/uploads/images/67b9b488-ce87-4e12-9d95-76bd6eb45968.webp differ diff --git a/backend/uploads/images/67e8354f-4687-4a52-9e6b-ab8c96f21f96.webp b/backend/uploads/images/67e8354f-4687-4a52-9e6b-ab8c96f21f96.webp new file mode 100644 index 0000000..c2527df Binary files /dev/null and b/backend/uploads/images/67e8354f-4687-4a52-9e6b-ab8c96f21f96.webp differ diff --git a/backend/uploads/images/6802b013-c1f0-491f-a455-2710fbe64dd4.webp b/backend/uploads/images/6802b013-c1f0-491f-a455-2710fbe64dd4.webp new file mode 100644 index 0000000..ee03602 Binary files /dev/null and b/backend/uploads/images/6802b013-c1f0-491f-a455-2710fbe64dd4.webp differ diff --git a/backend/uploads/images/6862840f-b677-4b47-8062-d9b333b8d30e.webp b/backend/uploads/images/6862840f-b677-4b47-8062-d9b333b8d30e.webp new file mode 100644 index 0000000..801648f Binary files /dev/null and b/backend/uploads/images/6862840f-b677-4b47-8062-d9b333b8d30e.webp differ diff --git a/backend/uploads/images/68f2332e-57b7-4afb-8627-6b01b37de606.webp b/backend/uploads/images/68f2332e-57b7-4afb-8627-6b01b37de606.webp new file mode 100644 index 0000000..afd75b9 Binary files /dev/null and b/backend/uploads/images/68f2332e-57b7-4afb-8627-6b01b37de606.webp differ diff --git a/backend/uploads/images/69da65d6-0432-4a88-a503-cee518a163e0.webp b/backend/uploads/images/69da65d6-0432-4a88-a503-cee518a163e0.webp new file mode 100644 index 0000000..f97495d Binary files /dev/null and b/backend/uploads/images/69da65d6-0432-4a88-a503-cee518a163e0.webp differ diff --git a/backend/uploads/images/6a18dea3-9c9c-4fbc-aefc-3952a6e095d8.webp b/backend/uploads/images/6a18dea3-9c9c-4fbc-aefc-3952a6e095d8.webp new file mode 100644 index 0000000..b38caee Binary files /dev/null and b/backend/uploads/images/6a18dea3-9c9c-4fbc-aefc-3952a6e095d8.webp differ diff --git a/backend/uploads/images/6a8f1725-96db-425d-8637-2fccd00fbcfe.webp b/backend/uploads/images/6a8f1725-96db-425d-8637-2fccd00fbcfe.webp new file mode 100644 index 0000000..801bb7f Binary files /dev/null and b/backend/uploads/images/6a8f1725-96db-425d-8637-2fccd00fbcfe.webp differ diff --git a/backend/uploads/images/6b1a93f7-a19e-4c7b-b9b6-a613884676e3.webp b/backend/uploads/images/6b1a93f7-a19e-4c7b-b9b6-a613884676e3.webp new file mode 100644 index 0000000..bdfab69 Binary files /dev/null and b/backend/uploads/images/6b1a93f7-a19e-4c7b-b9b6-a613884676e3.webp differ diff --git a/backend/uploads/images/6b3939fc-c648-4735-b723-68074eccce7a.webp b/backend/uploads/images/6b3939fc-c648-4735-b723-68074eccce7a.webp new file mode 100644 index 0000000..1cc1cd5 Binary files /dev/null and b/backend/uploads/images/6b3939fc-c648-4735-b723-68074eccce7a.webp differ diff --git a/backend/uploads/images/6b6a873e-b151-40d7-833e-33e4c14d6ccc.webp b/backend/uploads/images/6b6a873e-b151-40d7-833e-33e4c14d6ccc.webp new file mode 100644 index 0000000..be14a81 Binary files /dev/null and b/backend/uploads/images/6b6a873e-b151-40d7-833e-33e4c14d6ccc.webp differ diff --git a/backend/uploads/images/6c1b572f-be92-476d-b53e-67729f6bd8de.webp b/backend/uploads/images/6c1b572f-be92-476d-b53e-67729f6bd8de.webp new file mode 100644 index 0000000..24bef16 Binary files /dev/null and b/backend/uploads/images/6c1b572f-be92-476d-b53e-67729f6bd8de.webp differ diff --git a/backend/uploads/images/6c5b091a-dac2-4584-9479-9d869a99c646.webp b/backend/uploads/images/6c5b091a-dac2-4584-9479-9d869a99c646.webp new file mode 100644 index 0000000..9e6061c Binary files /dev/null and b/backend/uploads/images/6c5b091a-dac2-4584-9479-9d869a99c646.webp differ diff --git a/backend/uploads/images/6cbc1536-7713-454e-9b64-978ae63dbfd0.webp b/backend/uploads/images/6cbc1536-7713-454e-9b64-978ae63dbfd0.webp new file mode 100644 index 0000000..fffb138 Binary files /dev/null and b/backend/uploads/images/6cbc1536-7713-454e-9b64-978ae63dbfd0.webp differ diff --git a/backend/uploads/images/6cf372dd-2747-4af0-85cc-6cb93e1ce744.webp b/backend/uploads/images/6cf372dd-2747-4af0-85cc-6cb93e1ce744.webp new file mode 100644 index 0000000..0aae3d3 Binary files /dev/null and b/backend/uploads/images/6cf372dd-2747-4af0-85cc-6cb93e1ce744.webp differ diff --git a/backend/uploads/images/6d0abb92-17e9-4cec-84e7-65542ef1ebe3.webp b/backend/uploads/images/6d0abb92-17e9-4cec-84e7-65542ef1ebe3.webp new file mode 100644 index 0000000..1a51c27 Binary files /dev/null and b/backend/uploads/images/6d0abb92-17e9-4cec-84e7-65542ef1ebe3.webp differ diff --git a/backend/uploads/images/6e2fd5a3-d417-4e53-a452-65cb9507a100.webp b/backend/uploads/images/6e2fd5a3-d417-4e53-a452-65cb9507a100.webp new file mode 100644 index 0000000..bb731ef Binary files /dev/null and b/backend/uploads/images/6e2fd5a3-d417-4e53-a452-65cb9507a100.webp differ diff --git a/backend/uploads/images/6e453c50-2acd-407a-aa12-f1b053e279ea.webp b/backend/uploads/images/6e453c50-2acd-407a-aa12-f1b053e279ea.webp new file mode 100644 index 0000000..04c3d14 Binary files /dev/null and b/backend/uploads/images/6e453c50-2acd-407a-aa12-f1b053e279ea.webp differ diff --git a/backend/uploads/images/6e841274-2c55-4745-9134-5d4cae0728bc.webp b/backend/uploads/images/6e841274-2c55-4745-9134-5d4cae0728bc.webp new file mode 100644 index 0000000..4ef7858 Binary files /dev/null and b/backend/uploads/images/6e841274-2c55-4745-9134-5d4cae0728bc.webp differ diff --git a/backend/uploads/images/6ec29981-4d3e-4304-be2d-9259b47fa4db.webp b/backend/uploads/images/6ec29981-4d3e-4304-be2d-9259b47fa4db.webp new file mode 100644 index 0000000..07149a7 Binary files /dev/null and b/backend/uploads/images/6ec29981-4d3e-4304-be2d-9259b47fa4db.webp differ diff --git a/backend/uploads/images/6f67fe97-10a4-46bd-905c-60d62cc2dc19.webp b/backend/uploads/images/6f67fe97-10a4-46bd-905c-60d62cc2dc19.webp new file mode 100644 index 0000000..1f68992 Binary files /dev/null and b/backend/uploads/images/6f67fe97-10a4-46bd-905c-60d62cc2dc19.webp differ diff --git a/backend/uploads/images/6fa58568-d4a6-4a96-b871-3a6b8df8a843.webp b/backend/uploads/images/6fa58568-d4a6-4a96-b871-3a6b8df8a843.webp new file mode 100644 index 0000000..95d41dd Binary files /dev/null and b/backend/uploads/images/6fa58568-d4a6-4a96-b871-3a6b8df8a843.webp differ diff --git a/backend/uploads/images/6fb577be-689e-4dc7-ad19-1eed0565b2d1.webp b/backend/uploads/images/6fb577be-689e-4dc7-ad19-1eed0565b2d1.webp new file mode 100644 index 0000000..5d37a10 Binary files /dev/null and b/backend/uploads/images/6fb577be-689e-4dc7-ad19-1eed0565b2d1.webp differ diff --git a/backend/uploads/images/7025c81b-238d-4f33-8fde-cda13e18bbd2.webp b/backend/uploads/images/7025c81b-238d-4f33-8fde-cda13e18bbd2.webp new file mode 100644 index 0000000..7ea00ac Binary files /dev/null and b/backend/uploads/images/7025c81b-238d-4f33-8fde-cda13e18bbd2.webp differ diff --git a/backend/uploads/images/7033e62e-3dc9-408c-95c1-d93e5e88641e.webp b/backend/uploads/images/7033e62e-3dc9-408c-95c1-d93e5e88641e.webp new file mode 100644 index 0000000..606349e Binary files /dev/null and b/backend/uploads/images/7033e62e-3dc9-408c-95c1-d93e5e88641e.webp differ diff --git a/backend/uploads/images/70b2a8bf-cc9b-4b3c-9347-3d00cdf988f9.webp b/backend/uploads/images/70b2a8bf-cc9b-4b3c-9347-3d00cdf988f9.webp new file mode 100644 index 0000000..d437a0c Binary files /dev/null and b/backend/uploads/images/70b2a8bf-cc9b-4b3c-9347-3d00cdf988f9.webp differ diff --git a/backend/uploads/images/70e1b72e-0f84-4e6e-bcb7-2670fe4e2eff.webp b/backend/uploads/images/70e1b72e-0f84-4e6e-bcb7-2670fe4e2eff.webp new file mode 100644 index 0000000..0b4f599 Binary files /dev/null and b/backend/uploads/images/70e1b72e-0f84-4e6e-bcb7-2670fe4e2eff.webp differ diff --git a/backend/uploads/images/7100e01f-1a5b-4be3-a522-9117e4d916a0.webp b/backend/uploads/images/7100e01f-1a5b-4be3-a522-9117e4d916a0.webp new file mode 100644 index 0000000..6d7c16f Binary files /dev/null and b/backend/uploads/images/7100e01f-1a5b-4be3-a522-9117e4d916a0.webp differ diff --git a/backend/uploads/images/7176e762-37f2-4f53-ad1d-3b43a07743bd.webp b/backend/uploads/images/7176e762-37f2-4f53-ad1d-3b43a07743bd.webp new file mode 100644 index 0000000..e520de9 Binary files /dev/null and b/backend/uploads/images/7176e762-37f2-4f53-ad1d-3b43a07743bd.webp differ diff --git a/backend/uploads/images/718b920a-e3c2-4e5b-b6d2-eca96a113777.webp b/backend/uploads/images/718b920a-e3c2-4e5b-b6d2-eca96a113777.webp new file mode 100644 index 0000000..53cffd8 Binary files /dev/null and b/backend/uploads/images/718b920a-e3c2-4e5b-b6d2-eca96a113777.webp differ diff --git a/backend/uploads/images/71b3af27-06bd-4b00-b0a9-84a7b68275e4.webp b/backend/uploads/images/71b3af27-06bd-4b00-b0a9-84a7b68275e4.webp new file mode 100644 index 0000000..7a6d1af Binary files /dev/null and b/backend/uploads/images/71b3af27-06bd-4b00-b0a9-84a7b68275e4.webp differ diff --git a/backend/uploads/images/71c6092b-db72-4211-9591-8c9ac5c991bc.webp b/backend/uploads/images/71c6092b-db72-4211-9591-8c9ac5c991bc.webp new file mode 100644 index 0000000..8974e12 Binary files /dev/null and b/backend/uploads/images/71c6092b-db72-4211-9591-8c9ac5c991bc.webp differ diff --git a/backend/uploads/images/71f16dd8-5e8e-408c-b695-efb8c94a94a5.webp b/backend/uploads/images/71f16dd8-5e8e-408c-b695-efb8c94a94a5.webp new file mode 100644 index 0000000..32ebdd7 Binary files /dev/null and b/backend/uploads/images/71f16dd8-5e8e-408c-b695-efb8c94a94a5.webp differ diff --git a/backend/uploads/images/729c5825-4d63-4650-96d4-2fe412785ff0.webp b/backend/uploads/images/729c5825-4d63-4650-96d4-2fe412785ff0.webp new file mode 100644 index 0000000..16b2de0 Binary files /dev/null and b/backend/uploads/images/729c5825-4d63-4650-96d4-2fe412785ff0.webp differ diff --git a/backend/uploads/images/731fb987-b6f0-4288-a5c8-b26cefbc2f39.webp b/backend/uploads/images/731fb987-b6f0-4288-a5c8-b26cefbc2f39.webp new file mode 100644 index 0000000..1e856cd Binary files /dev/null and b/backend/uploads/images/731fb987-b6f0-4288-a5c8-b26cefbc2f39.webp differ diff --git a/backend/uploads/images/7350f1c5-2a55-4a25-90d3-b58ebcc6c46b.webp b/backend/uploads/images/7350f1c5-2a55-4a25-90d3-b58ebcc6c46b.webp new file mode 100644 index 0000000..9a58e12 Binary files /dev/null and b/backend/uploads/images/7350f1c5-2a55-4a25-90d3-b58ebcc6c46b.webp differ diff --git a/backend/uploads/images/73815fb4-0190-4296-9db0-866f98a46c5d.webp b/backend/uploads/images/73815fb4-0190-4296-9db0-866f98a46c5d.webp new file mode 100644 index 0000000..70a174e Binary files /dev/null and b/backend/uploads/images/73815fb4-0190-4296-9db0-866f98a46c5d.webp differ diff --git a/backend/uploads/images/73a4b8b4-9071-48d9-9231-977cf3134732.webp b/backend/uploads/images/73a4b8b4-9071-48d9-9231-977cf3134732.webp new file mode 100644 index 0000000..6719aaa Binary files /dev/null and b/backend/uploads/images/73a4b8b4-9071-48d9-9231-977cf3134732.webp differ diff --git a/backend/uploads/images/73b2aebc-786c-4049-a830-819b04dbc2c6.webp b/backend/uploads/images/73b2aebc-786c-4049-a830-819b04dbc2c6.webp new file mode 100644 index 0000000..da511d5 Binary files /dev/null and b/backend/uploads/images/73b2aebc-786c-4049-a830-819b04dbc2c6.webp differ diff --git a/backend/uploads/images/7442d490-165b-4d35-9ffa-0fb3c4ff05b6.webp b/backend/uploads/images/7442d490-165b-4d35-9ffa-0fb3c4ff05b6.webp new file mode 100644 index 0000000..974c1ae Binary files /dev/null and b/backend/uploads/images/7442d490-165b-4d35-9ffa-0fb3c4ff05b6.webp differ diff --git a/backend/uploads/images/7464eba4-065a-4c97-aba4-271df5c5d96a.webp b/backend/uploads/images/7464eba4-065a-4c97-aba4-271df5c5d96a.webp new file mode 100644 index 0000000..e16f859 Binary files /dev/null and b/backend/uploads/images/7464eba4-065a-4c97-aba4-271df5c5d96a.webp differ diff --git a/backend/uploads/images/7493d0a5-551f-4c53-8557-30e41760baa6.webp b/backend/uploads/images/7493d0a5-551f-4c53-8557-30e41760baa6.webp new file mode 100644 index 0000000..f810748 Binary files /dev/null and b/backend/uploads/images/7493d0a5-551f-4c53-8557-30e41760baa6.webp differ diff --git a/backend/uploads/images/74fb717a-7f89-475b-8789-e5c2bf77b888.webp b/backend/uploads/images/74fb717a-7f89-475b-8789-e5c2bf77b888.webp new file mode 100644 index 0000000..ef602dc Binary files /dev/null and b/backend/uploads/images/74fb717a-7f89-475b-8789-e5c2bf77b888.webp differ diff --git a/backend/uploads/images/75171a59-0ead-42e0-9f91-6a733449c900.webp b/backend/uploads/images/75171a59-0ead-42e0-9f91-6a733449c900.webp new file mode 100644 index 0000000..83501ab Binary files /dev/null and b/backend/uploads/images/75171a59-0ead-42e0-9f91-6a733449c900.webp differ diff --git a/backend/uploads/images/75274797-9c11-4809-844a-6c1aaf5b1c4c.webp b/backend/uploads/images/75274797-9c11-4809-844a-6c1aaf5b1c4c.webp new file mode 100644 index 0000000..c56a915 Binary files /dev/null and b/backend/uploads/images/75274797-9c11-4809-844a-6c1aaf5b1c4c.webp differ diff --git a/backend/uploads/images/75b65636-8fc3-48e0-8586-49e3ec1e2469.webp b/backend/uploads/images/75b65636-8fc3-48e0-8586-49e3ec1e2469.webp new file mode 100644 index 0000000..86810f8 Binary files /dev/null and b/backend/uploads/images/75b65636-8fc3-48e0-8586-49e3ec1e2469.webp differ diff --git a/backend/uploads/images/75e0dcc0-4a3e-4598-a4f0-81947efaa60e.webp b/backend/uploads/images/75e0dcc0-4a3e-4598-a4f0-81947efaa60e.webp new file mode 100644 index 0000000..6522440 Binary files /dev/null and b/backend/uploads/images/75e0dcc0-4a3e-4598-a4f0-81947efaa60e.webp differ diff --git a/backend/uploads/images/75e23dd9-1472-485c-9bfe-bb839f372a3b.webp b/backend/uploads/images/75e23dd9-1472-485c-9bfe-bb839f372a3b.webp new file mode 100644 index 0000000..7a52dac Binary files /dev/null and b/backend/uploads/images/75e23dd9-1472-485c-9bfe-bb839f372a3b.webp differ diff --git a/backend/uploads/images/75f06e49-299b-498e-a424-8f31e86132d6.webp b/backend/uploads/images/75f06e49-299b-498e-a424-8f31e86132d6.webp new file mode 100644 index 0000000..49d1d5d Binary files /dev/null and b/backend/uploads/images/75f06e49-299b-498e-a424-8f31e86132d6.webp differ diff --git a/backend/uploads/images/76100af7-b2b4-4446-9e25-781bffdb3dab.webp b/backend/uploads/images/76100af7-b2b4-4446-9e25-781bffdb3dab.webp new file mode 100644 index 0000000..f646cf7 Binary files /dev/null and b/backend/uploads/images/76100af7-b2b4-4446-9e25-781bffdb3dab.webp differ diff --git a/backend/uploads/images/76ac7623-192d-4da7-b945-c2e8e63844be.webp b/backend/uploads/images/76ac7623-192d-4da7-b945-c2e8e63844be.webp new file mode 100644 index 0000000..06f9514 Binary files /dev/null and b/backend/uploads/images/76ac7623-192d-4da7-b945-c2e8e63844be.webp differ diff --git a/backend/uploads/images/76e0e798-b75f-438c-b746-96d4d87e1e79.webp b/backend/uploads/images/76e0e798-b75f-438c-b746-96d4d87e1e79.webp new file mode 100644 index 0000000..054d98e Binary files /dev/null and b/backend/uploads/images/76e0e798-b75f-438c-b746-96d4d87e1e79.webp differ diff --git a/backend/uploads/images/770273f9-26f5-458d-98d5-f934ee2c9abb.webp b/backend/uploads/images/770273f9-26f5-458d-98d5-f934ee2c9abb.webp new file mode 100644 index 0000000..b1ec4d3 Binary files /dev/null and b/backend/uploads/images/770273f9-26f5-458d-98d5-f934ee2c9abb.webp differ diff --git a/backend/uploads/images/77326349-f463-43a0-8a2f-eb29b42a47fe.webp b/backend/uploads/images/77326349-f463-43a0-8a2f-eb29b42a47fe.webp new file mode 100644 index 0000000..c0e5157 Binary files /dev/null and b/backend/uploads/images/77326349-f463-43a0-8a2f-eb29b42a47fe.webp differ diff --git a/backend/uploads/images/775aec0b-7862-4ed9-afbe-be2ee3745b25.webp b/backend/uploads/images/775aec0b-7862-4ed9-afbe-be2ee3745b25.webp new file mode 100644 index 0000000..b506875 Binary files /dev/null and b/backend/uploads/images/775aec0b-7862-4ed9-afbe-be2ee3745b25.webp differ diff --git a/backend/uploads/images/77b9d307-44d6-46f3-b4d1-12534f297ab0.webp b/backend/uploads/images/77b9d307-44d6-46f3-b4d1-12534f297ab0.webp new file mode 100644 index 0000000..9351b0e Binary files /dev/null and b/backend/uploads/images/77b9d307-44d6-46f3-b4d1-12534f297ab0.webp differ diff --git a/backend/uploads/images/781502b0-3857-4528-9951-0403d2deba5b.webp b/backend/uploads/images/781502b0-3857-4528-9951-0403d2deba5b.webp new file mode 100644 index 0000000..41def1a Binary files /dev/null and b/backend/uploads/images/781502b0-3857-4528-9951-0403d2deba5b.webp differ diff --git a/backend/uploads/images/7824a6bb-15be-48f7-9c5e-b488aae6b5c6.webp b/backend/uploads/images/7824a6bb-15be-48f7-9c5e-b488aae6b5c6.webp new file mode 100644 index 0000000..c360c8d Binary files /dev/null and b/backend/uploads/images/7824a6bb-15be-48f7-9c5e-b488aae6b5c6.webp differ diff --git a/backend/uploads/images/7824e37b-5d0f-4570-b8d6-afb077773f81.webp b/backend/uploads/images/7824e37b-5d0f-4570-b8d6-afb077773f81.webp new file mode 100644 index 0000000..59639cc Binary files /dev/null and b/backend/uploads/images/7824e37b-5d0f-4570-b8d6-afb077773f81.webp differ diff --git a/backend/uploads/images/78798d19-d85b-46d9-a48d-3178898c702d.webp b/backend/uploads/images/78798d19-d85b-46d9-a48d-3178898c702d.webp new file mode 100644 index 0000000..8126181 Binary files /dev/null and b/backend/uploads/images/78798d19-d85b-46d9-a48d-3178898c702d.webp differ diff --git a/backend/uploads/images/78e574a1-2e3d-4e73-8c16-d13210e61efe.webp b/backend/uploads/images/78e574a1-2e3d-4e73-8c16-d13210e61efe.webp new file mode 100644 index 0000000..287e71d Binary files /dev/null and b/backend/uploads/images/78e574a1-2e3d-4e73-8c16-d13210e61efe.webp differ diff --git a/backend/uploads/images/78feb944-92c2-4533-b427-8e4a61fdb470.webp b/backend/uploads/images/78feb944-92c2-4533-b427-8e4a61fdb470.webp new file mode 100644 index 0000000..4c83860 Binary files /dev/null and b/backend/uploads/images/78feb944-92c2-4533-b427-8e4a61fdb470.webp differ diff --git a/backend/uploads/images/7965d2d8-1cda-4471-a59c-3bad77868a65.webp b/backend/uploads/images/7965d2d8-1cda-4471-a59c-3bad77868a65.webp new file mode 100644 index 0000000..b746ef9 Binary files /dev/null and b/backend/uploads/images/7965d2d8-1cda-4471-a59c-3bad77868a65.webp differ diff --git a/backend/uploads/images/7a05e4d7-d283-4960-a4a6-638bb6f4c557.webp b/backend/uploads/images/7a05e4d7-d283-4960-a4a6-638bb6f4c557.webp new file mode 100644 index 0000000..25a0d91 Binary files /dev/null and b/backend/uploads/images/7a05e4d7-d283-4960-a4a6-638bb6f4c557.webp differ diff --git a/backend/uploads/images/7a0cf298-e151-45a4-87d1-34fb4c6f032c.webp b/backend/uploads/images/7a0cf298-e151-45a4-87d1-34fb4c6f032c.webp new file mode 100644 index 0000000..9b5ac27 Binary files /dev/null and b/backend/uploads/images/7a0cf298-e151-45a4-87d1-34fb4c6f032c.webp differ diff --git a/backend/uploads/images/7a576314-a517-450f-b863-0e67fd96d940.webp b/backend/uploads/images/7a576314-a517-450f-b863-0e67fd96d940.webp new file mode 100644 index 0000000..74ffb49 Binary files /dev/null and b/backend/uploads/images/7a576314-a517-450f-b863-0e67fd96d940.webp differ diff --git a/backend/uploads/images/7a744ad6-ea4d-4eb9-aa6d-3b487d855943.webp b/backend/uploads/images/7a744ad6-ea4d-4eb9-aa6d-3b487d855943.webp new file mode 100644 index 0000000..68fa3c0 Binary files /dev/null and b/backend/uploads/images/7a744ad6-ea4d-4eb9-aa6d-3b487d855943.webp differ diff --git a/backend/uploads/images/7aa04ba4-4903-47d3-9197-f14f2d7ea504.webp b/backend/uploads/images/7aa04ba4-4903-47d3-9197-f14f2d7ea504.webp new file mode 100644 index 0000000..d91c3be Binary files /dev/null and b/backend/uploads/images/7aa04ba4-4903-47d3-9197-f14f2d7ea504.webp differ diff --git a/backend/uploads/images/7b72506e-d44f-4eed-92bf-b445f30b4643.webp b/backend/uploads/images/7b72506e-d44f-4eed-92bf-b445f30b4643.webp new file mode 100644 index 0000000..8f2bacb Binary files /dev/null and b/backend/uploads/images/7b72506e-d44f-4eed-92bf-b445f30b4643.webp differ diff --git a/backend/uploads/images/7ba68449-4b08-4f59-be66-633b08140638.webp b/backend/uploads/images/7ba68449-4b08-4f59-be66-633b08140638.webp new file mode 100644 index 0000000..b1e2786 Binary files /dev/null and b/backend/uploads/images/7ba68449-4b08-4f59-be66-633b08140638.webp differ diff --git a/backend/uploads/images/7c0ec897-477e-48ad-b98f-1989e6dbe3f6.webp b/backend/uploads/images/7c0ec897-477e-48ad-b98f-1989e6dbe3f6.webp new file mode 100644 index 0000000..4de42f7 Binary files /dev/null and b/backend/uploads/images/7c0ec897-477e-48ad-b98f-1989e6dbe3f6.webp differ diff --git a/backend/uploads/images/7c13695f-ee2d-4583-b987-d1992eed9d75.webp b/backend/uploads/images/7c13695f-ee2d-4583-b987-d1992eed9d75.webp new file mode 100644 index 0000000..809ec12 Binary files /dev/null and b/backend/uploads/images/7c13695f-ee2d-4583-b987-d1992eed9d75.webp differ diff --git a/backend/uploads/images/7ce2a109-8b1f-4130-9812-8928a9e0c626.webp b/backend/uploads/images/7ce2a109-8b1f-4130-9812-8928a9e0c626.webp new file mode 100644 index 0000000..8346762 Binary files /dev/null and b/backend/uploads/images/7ce2a109-8b1f-4130-9812-8928a9e0c626.webp differ diff --git a/backend/uploads/images/7d28a3e1-3cec-4055-806f-1c687772dcb1.webp b/backend/uploads/images/7d28a3e1-3cec-4055-806f-1c687772dcb1.webp new file mode 100644 index 0000000..eea403f Binary files /dev/null and b/backend/uploads/images/7d28a3e1-3cec-4055-806f-1c687772dcb1.webp differ diff --git a/backend/uploads/images/7d553458-9259-4049-9379-e6bb1e02364b.webp b/backend/uploads/images/7d553458-9259-4049-9379-e6bb1e02364b.webp new file mode 100644 index 0000000..50ed27f Binary files /dev/null and b/backend/uploads/images/7d553458-9259-4049-9379-e6bb1e02364b.webp differ diff --git a/backend/uploads/images/7d93a8c8-a0c6-417a-b21a-a8727b6c3422.webp b/backend/uploads/images/7d93a8c8-a0c6-417a-b21a-a8727b6c3422.webp new file mode 100644 index 0000000..70c08ae Binary files /dev/null and b/backend/uploads/images/7d93a8c8-a0c6-417a-b21a-a8727b6c3422.webp differ diff --git a/backend/uploads/images/7daf8fb8-8095-4480-9061-73303a2601c7.webp b/backend/uploads/images/7daf8fb8-8095-4480-9061-73303a2601c7.webp new file mode 100644 index 0000000..42f16a9 Binary files /dev/null and b/backend/uploads/images/7daf8fb8-8095-4480-9061-73303a2601c7.webp differ diff --git a/backend/uploads/images/7dc9a835-a66f-4d50-a9ed-9ad0268e16bf.webp b/backend/uploads/images/7dc9a835-a66f-4d50-a9ed-9ad0268e16bf.webp new file mode 100644 index 0000000..0064fc6 Binary files /dev/null and b/backend/uploads/images/7dc9a835-a66f-4d50-a9ed-9ad0268e16bf.webp differ diff --git a/backend/uploads/images/7dcdcc38-589f-472b-8e64-8c731665ee1b.webp b/backend/uploads/images/7dcdcc38-589f-472b-8e64-8c731665ee1b.webp new file mode 100644 index 0000000..b0e6083 Binary files /dev/null and b/backend/uploads/images/7dcdcc38-589f-472b-8e64-8c731665ee1b.webp differ diff --git a/backend/uploads/images/7f30e5fb-9af1-4c7c-898b-d4b3c11efcc1.webp b/backend/uploads/images/7f30e5fb-9af1-4c7c-898b-d4b3c11efcc1.webp new file mode 100644 index 0000000..f4d9821 Binary files /dev/null and b/backend/uploads/images/7f30e5fb-9af1-4c7c-898b-d4b3c11efcc1.webp differ diff --git a/backend/uploads/images/7f4e5e5e-9542-445b-9bb1-92f0ec16651d.webp b/backend/uploads/images/7f4e5e5e-9542-445b-9bb1-92f0ec16651d.webp new file mode 100644 index 0000000..4be6107 Binary files /dev/null and b/backend/uploads/images/7f4e5e5e-9542-445b-9bb1-92f0ec16651d.webp differ diff --git a/backend/uploads/images/7f5b7ee1-09e3-4553-8fe7-c19ee033d36e.webp b/backend/uploads/images/7f5b7ee1-09e3-4553-8fe7-c19ee033d36e.webp new file mode 100644 index 0000000..7b8f3de Binary files /dev/null and b/backend/uploads/images/7f5b7ee1-09e3-4553-8fe7-c19ee033d36e.webp differ diff --git a/backend/uploads/images/80281598-f095-4d59-a63b-cf1976f83a38.webp b/backend/uploads/images/80281598-f095-4d59-a63b-cf1976f83a38.webp new file mode 100644 index 0000000..b63809a Binary files /dev/null and b/backend/uploads/images/80281598-f095-4d59-a63b-cf1976f83a38.webp differ diff --git a/backend/uploads/images/805a44e1-f583-4006-a21d-f8441d22bec7.webp b/backend/uploads/images/805a44e1-f583-4006-a21d-f8441d22bec7.webp new file mode 100644 index 0000000..18e7172 Binary files /dev/null and b/backend/uploads/images/805a44e1-f583-4006-a21d-f8441d22bec7.webp differ diff --git a/backend/uploads/images/8067d436-c765-4ea2-8392-3ed5dc84a5b8.webp b/backend/uploads/images/8067d436-c765-4ea2-8392-3ed5dc84a5b8.webp new file mode 100644 index 0000000..d6f1f46 Binary files /dev/null and b/backend/uploads/images/8067d436-c765-4ea2-8392-3ed5dc84a5b8.webp differ diff --git a/backend/uploads/images/80a6f145-8ef2-4808-89cb-cff32fc93891.webp b/backend/uploads/images/80a6f145-8ef2-4808-89cb-cff32fc93891.webp new file mode 100644 index 0000000..5dd844d Binary files /dev/null and b/backend/uploads/images/80a6f145-8ef2-4808-89cb-cff32fc93891.webp differ diff --git a/backend/uploads/images/80b64f6b-f560-4383-a9f8-a34048599589.webp b/backend/uploads/images/80b64f6b-f560-4383-a9f8-a34048599589.webp new file mode 100644 index 0000000..43c2db9 Binary files /dev/null and b/backend/uploads/images/80b64f6b-f560-4383-a9f8-a34048599589.webp differ diff --git a/backend/uploads/images/80d060be-06cb-47ec-be0e-0dddf91f1ec6.webp b/backend/uploads/images/80d060be-06cb-47ec-be0e-0dddf91f1ec6.webp new file mode 100644 index 0000000..7d61d84 Binary files /dev/null and b/backend/uploads/images/80d060be-06cb-47ec-be0e-0dddf91f1ec6.webp differ diff --git a/backend/uploads/images/81019901-9169-4a9a-a262-1d3214c3e2b4.webp b/backend/uploads/images/81019901-9169-4a9a-a262-1d3214c3e2b4.webp new file mode 100644 index 0000000..c345654 Binary files /dev/null and b/backend/uploads/images/81019901-9169-4a9a-a262-1d3214c3e2b4.webp differ diff --git a/backend/uploads/images/8103d981-b60e-4164-8708-10655a399af6.webp b/backend/uploads/images/8103d981-b60e-4164-8708-10655a399af6.webp new file mode 100644 index 0000000..727c636 Binary files /dev/null and b/backend/uploads/images/8103d981-b60e-4164-8708-10655a399af6.webp differ diff --git a/backend/uploads/images/81092b6a-f2dd-464a-bc81-8574d9cc5773.webp b/backend/uploads/images/81092b6a-f2dd-464a-bc81-8574d9cc5773.webp new file mode 100644 index 0000000..e66a144 Binary files /dev/null and b/backend/uploads/images/81092b6a-f2dd-464a-bc81-8574d9cc5773.webp differ diff --git a/backend/uploads/images/814f0b0e-b43e-44b5-82e2-0ed890f67f26.webp b/backend/uploads/images/814f0b0e-b43e-44b5-82e2-0ed890f67f26.webp new file mode 100644 index 0000000..bae0f8d Binary files /dev/null and b/backend/uploads/images/814f0b0e-b43e-44b5-82e2-0ed890f67f26.webp differ diff --git a/backend/uploads/images/815de449-fcea-4280-ac0f-e440cf965db1.webp b/backend/uploads/images/815de449-fcea-4280-ac0f-e440cf965db1.webp new file mode 100644 index 0000000..37640ab Binary files /dev/null and b/backend/uploads/images/815de449-fcea-4280-ac0f-e440cf965db1.webp differ diff --git a/backend/uploads/images/82704132-f0ed-4e4f-9936-d62af6f16b3e.webp b/backend/uploads/images/82704132-f0ed-4e4f-9936-d62af6f16b3e.webp new file mode 100644 index 0000000..c1aa3f2 Binary files /dev/null and b/backend/uploads/images/82704132-f0ed-4e4f-9936-d62af6f16b3e.webp differ diff --git a/backend/uploads/images/82fbc2b1-b083-48aa-bd37-49e6f542537b.webp b/backend/uploads/images/82fbc2b1-b083-48aa-bd37-49e6f542537b.webp new file mode 100644 index 0000000..0379f2a Binary files /dev/null and b/backend/uploads/images/82fbc2b1-b083-48aa-bd37-49e6f542537b.webp differ diff --git a/backend/uploads/images/8333f28b-fba2-4fbc-afbb-8c32b1e81480.webp b/backend/uploads/images/8333f28b-fba2-4fbc-afbb-8c32b1e81480.webp new file mode 100644 index 0000000..8a12daa Binary files /dev/null and b/backend/uploads/images/8333f28b-fba2-4fbc-afbb-8c32b1e81480.webp differ diff --git a/backend/uploads/images/84242eba-eecb-497c-b32c-0e7d8b1a9fee.webp b/backend/uploads/images/84242eba-eecb-497c-b32c-0e7d8b1a9fee.webp new file mode 100644 index 0000000..ef75cea Binary files /dev/null and b/backend/uploads/images/84242eba-eecb-497c-b32c-0e7d8b1a9fee.webp differ diff --git a/backend/uploads/images/84bb51a8-bba6-44a8-8ec6-384c15f0a5fb.webp b/backend/uploads/images/84bb51a8-bba6-44a8-8ec6-384c15f0a5fb.webp new file mode 100644 index 0000000..f321fb5 Binary files /dev/null and b/backend/uploads/images/84bb51a8-bba6-44a8-8ec6-384c15f0a5fb.webp differ diff --git a/backend/uploads/images/84c349ea-65b3-4226-813f-aea07622214a.webp b/backend/uploads/images/84c349ea-65b3-4226-813f-aea07622214a.webp new file mode 100644 index 0000000..22f0955 Binary files /dev/null and b/backend/uploads/images/84c349ea-65b3-4226-813f-aea07622214a.webp differ diff --git a/backend/uploads/images/84d06af1-ffba-4af5-8213-f329e5e30fe8.webp b/backend/uploads/images/84d06af1-ffba-4af5-8213-f329e5e30fe8.webp new file mode 100644 index 0000000..c683d9c Binary files /dev/null and b/backend/uploads/images/84d06af1-ffba-4af5-8213-f329e5e30fe8.webp differ diff --git a/backend/uploads/images/8554a8fe-59aa-48cd-bc98-b8864c119dc8.webp b/backend/uploads/images/8554a8fe-59aa-48cd-bc98-b8864c119dc8.webp new file mode 100644 index 0000000..964138c Binary files /dev/null and b/backend/uploads/images/8554a8fe-59aa-48cd-bc98-b8864c119dc8.webp differ diff --git a/backend/uploads/images/858b904c-577a-4c73-a71e-3e0e5f5e09d5.webp b/backend/uploads/images/858b904c-577a-4c73-a71e-3e0e5f5e09d5.webp new file mode 100644 index 0000000..a350068 Binary files /dev/null and b/backend/uploads/images/858b904c-577a-4c73-a71e-3e0e5f5e09d5.webp differ diff --git a/backend/uploads/images/86608a3c-6b35-42e4-af87-d5127a8e4e0c.webp b/backend/uploads/images/86608a3c-6b35-42e4-af87-d5127a8e4e0c.webp new file mode 100644 index 0000000..e488d5c Binary files /dev/null and b/backend/uploads/images/86608a3c-6b35-42e4-af87-d5127a8e4e0c.webp differ diff --git a/backend/uploads/images/8738632d-9e95-43e5-84c9-c368c34cab92.webp b/backend/uploads/images/8738632d-9e95-43e5-84c9-c368c34cab92.webp new file mode 100644 index 0000000..f2dc86f Binary files /dev/null and b/backend/uploads/images/8738632d-9e95-43e5-84c9-c368c34cab92.webp differ diff --git a/backend/uploads/images/87928c8c-747d-48f6-aacb-217d63cfe57b.webp b/backend/uploads/images/87928c8c-747d-48f6-aacb-217d63cfe57b.webp new file mode 100644 index 0000000..2a21ca5 Binary files /dev/null and b/backend/uploads/images/87928c8c-747d-48f6-aacb-217d63cfe57b.webp differ diff --git a/backend/uploads/images/87c62964-0039-4c59-99b8-18452fa34e07.webp b/backend/uploads/images/87c62964-0039-4c59-99b8-18452fa34e07.webp new file mode 100644 index 0000000..d19d1cb Binary files /dev/null and b/backend/uploads/images/87c62964-0039-4c59-99b8-18452fa34e07.webp differ diff --git a/backend/uploads/images/88d271bc-1cbf-464f-be56-b3789dd66fc6.webp b/backend/uploads/images/88d271bc-1cbf-464f-be56-b3789dd66fc6.webp new file mode 100644 index 0000000..4ff7280 Binary files /dev/null and b/backend/uploads/images/88d271bc-1cbf-464f-be56-b3789dd66fc6.webp differ diff --git a/backend/uploads/images/890d78e8-264d-47c2-a66e-93038b7051e8.webp b/backend/uploads/images/890d78e8-264d-47c2-a66e-93038b7051e8.webp new file mode 100644 index 0000000..aed550d Binary files /dev/null and b/backend/uploads/images/890d78e8-264d-47c2-a66e-93038b7051e8.webp differ diff --git a/backend/uploads/images/892bd48c-0eb5-4941-952d-5bcc675cdd9b.webp b/backend/uploads/images/892bd48c-0eb5-4941-952d-5bcc675cdd9b.webp new file mode 100644 index 0000000..8155123 Binary files /dev/null and b/backend/uploads/images/892bd48c-0eb5-4941-952d-5bcc675cdd9b.webp differ diff --git a/backend/uploads/images/89d56d41-ede5-481d-a025-0cffe4803697.webp b/backend/uploads/images/89d56d41-ede5-481d-a025-0cffe4803697.webp new file mode 100644 index 0000000..c55f597 Binary files /dev/null and b/backend/uploads/images/89d56d41-ede5-481d-a025-0cffe4803697.webp differ diff --git a/backend/uploads/images/89e86a74-afb1-471a-a5a0-c3b5157a2c98.webp b/backend/uploads/images/89e86a74-afb1-471a-a5a0-c3b5157a2c98.webp new file mode 100644 index 0000000..a90dc3e Binary files /dev/null and b/backend/uploads/images/89e86a74-afb1-471a-a5a0-c3b5157a2c98.webp differ diff --git a/backend/uploads/images/89f01a59-90d3-4b3e-b5b2-b3e731fb31aa.webp b/backend/uploads/images/89f01a59-90d3-4b3e-b5b2-b3e731fb31aa.webp new file mode 100644 index 0000000..2a83ff3 Binary files /dev/null and b/backend/uploads/images/89f01a59-90d3-4b3e-b5b2-b3e731fb31aa.webp differ diff --git a/backend/uploads/images/89f274e0-6f09-47a4-bacf-56a731a705ab.webp b/backend/uploads/images/89f274e0-6f09-47a4-bacf-56a731a705ab.webp new file mode 100644 index 0000000..ebe0b91 Binary files /dev/null and b/backend/uploads/images/89f274e0-6f09-47a4-bacf-56a731a705ab.webp differ diff --git a/backend/uploads/images/8a745255-ba94-42d4-a3b0-a9bbb40eb384.webp b/backend/uploads/images/8a745255-ba94-42d4-a3b0-a9bbb40eb384.webp new file mode 100644 index 0000000..a362109 Binary files /dev/null and b/backend/uploads/images/8a745255-ba94-42d4-a3b0-a9bbb40eb384.webp differ diff --git a/backend/uploads/images/8b85d79a-4ca3-46b3-ac2a-9d94b9ba5492.webp b/backend/uploads/images/8b85d79a-4ca3-46b3-ac2a-9d94b9ba5492.webp new file mode 100644 index 0000000..f6e33c2 Binary files /dev/null and b/backend/uploads/images/8b85d79a-4ca3-46b3-ac2a-9d94b9ba5492.webp differ diff --git a/backend/uploads/images/8b954b4e-d653-4979-8e2a-10d0ee6ad7bb.webp b/backend/uploads/images/8b954b4e-d653-4979-8e2a-10d0ee6ad7bb.webp new file mode 100644 index 0000000..d517441 Binary files /dev/null and b/backend/uploads/images/8b954b4e-d653-4979-8e2a-10d0ee6ad7bb.webp differ diff --git a/backend/uploads/images/8c29b3a8-6d02-4e8f-9025-f08ce5a72cd8.webp b/backend/uploads/images/8c29b3a8-6d02-4e8f-9025-f08ce5a72cd8.webp new file mode 100644 index 0000000..3607c45 Binary files /dev/null and b/backend/uploads/images/8c29b3a8-6d02-4e8f-9025-f08ce5a72cd8.webp differ diff --git a/backend/uploads/images/8c6e5932-713b-485a-951b-ef459e28eef3.webp b/backend/uploads/images/8c6e5932-713b-485a-951b-ef459e28eef3.webp new file mode 100644 index 0000000..1821319 Binary files /dev/null and b/backend/uploads/images/8c6e5932-713b-485a-951b-ef459e28eef3.webp differ diff --git a/backend/uploads/images/8c9cbac4-ccbc-416a-9cf1-9b0fbed545cf.webp b/backend/uploads/images/8c9cbac4-ccbc-416a-9cf1-9b0fbed545cf.webp new file mode 100644 index 0000000..fc2c833 Binary files /dev/null and b/backend/uploads/images/8c9cbac4-ccbc-416a-9cf1-9b0fbed545cf.webp differ diff --git a/backend/uploads/images/8e147d76-3dd2-45a6-99b6-c71adad71c19.webp b/backend/uploads/images/8e147d76-3dd2-45a6-99b6-c71adad71c19.webp new file mode 100644 index 0000000..2e4a25d Binary files /dev/null and b/backend/uploads/images/8e147d76-3dd2-45a6-99b6-c71adad71c19.webp differ diff --git a/backend/uploads/images/8e3807ff-c769-4225-b105-ef6c6433300d.webp b/backend/uploads/images/8e3807ff-c769-4225-b105-ef6c6433300d.webp new file mode 100644 index 0000000..607809e Binary files /dev/null and b/backend/uploads/images/8e3807ff-c769-4225-b105-ef6c6433300d.webp differ diff --git a/backend/uploads/images/8e6d6844-bf8d-4650-b764-bff02b939073.webp b/backend/uploads/images/8e6d6844-bf8d-4650-b764-bff02b939073.webp new file mode 100644 index 0000000..2892728 Binary files /dev/null and b/backend/uploads/images/8e6d6844-bf8d-4650-b764-bff02b939073.webp differ diff --git a/backend/uploads/images/8ee73fbf-d9a9-4f72-8da3-9cc65f299763.webp b/backend/uploads/images/8ee73fbf-d9a9-4f72-8da3-9cc65f299763.webp new file mode 100644 index 0000000..aa43f5e Binary files /dev/null and b/backend/uploads/images/8ee73fbf-d9a9-4f72-8da3-9cc65f299763.webp differ diff --git a/backend/uploads/images/8f6c1a7f-bb0c-4a12-825f-6ebcd2f9141d.webp b/backend/uploads/images/8f6c1a7f-bb0c-4a12-825f-6ebcd2f9141d.webp new file mode 100644 index 0000000..47fbb9b Binary files /dev/null and b/backend/uploads/images/8f6c1a7f-bb0c-4a12-825f-6ebcd2f9141d.webp differ diff --git a/backend/uploads/images/8f920676-a6ba-4efd-9229-e6f6f3d21d58.webp b/backend/uploads/images/8f920676-a6ba-4efd-9229-e6f6f3d21d58.webp new file mode 100644 index 0000000..a625c1d Binary files /dev/null and b/backend/uploads/images/8f920676-a6ba-4efd-9229-e6f6f3d21d58.webp differ diff --git a/backend/uploads/images/8fdaf71e-8b60-4d6a-b23e-0d939e4a01c5.webp b/backend/uploads/images/8fdaf71e-8b60-4d6a-b23e-0d939e4a01c5.webp new file mode 100644 index 0000000..ac2ecdf Binary files /dev/null and b/backend/uploads/images/8fdaf71e-8b60-4d6a-b23e-0d939e4a01c5.webp differ diff --git a/backend/uploads/images/90048a84-2a83-496a-b603-d2a6629c753a.webp b/backend/uploads/images/90048a84-2a83-496a-b603-d2a6629c753a.webp new file mode 100644 index 0000000..d7fc834 Binary files /dev/null and b/backend/uploads/images/90048a84-2a83-496a-b603-d2a6629c753a.webp differ diff --git a/backend/uploads/images/903ac7cd-3f32-4cba-b1b3-b9414957b95a.webp b/backend/uploads/images/903ac7cd-3f32-4cba-b1b3-b9414957b95a.webp new file mode 100644 index 0000000..7fee9cc Binary files /dev/null and b/backend/uploads/images/903ac7cd-3f32-4cba-b1b3-b9414957b95a.webp differ diff --git a/backend/uploads/images/90b40af4-9a5b-4d33-982b-c57852c68436.webp b/backend/uploads/images/90b40af4-9a5b-4d33-982b-c57852c68436.webp new file mode 100644 index 0000000..d8d8b5c Binary files /dev/null and b/backend/uploads/images/90b40af4-9a5b-4d33-982b-c57852c68436.webp differ diff --git a/backend/uploads/images/913b77b3-d6dc-43c2-bdbc-325dff7264da.webp b/backend/uploads/images/913b77b3-d6dc-43c2-bdbc-325dff7264da.webp new file mode 100644 index 0000000..052e11c Binary files /dev/null and b/backend/uploads/images/913b77b3-d6dc-43c2-bdbc-325dff7264da.webp differ diff --git a/backend/uploads/images/91b007a5-861e-4519-a63e-08f1fee5ef69.webp b/backend/uploads/images/91b007a5-861e-4519-a63e-08f1fee5ef69.webp new file mode 100644 index 0000000..6d24c8e Binary files /dev/null and b/backend/uploads/images/91b007a5-861e-4519-a63e-08f1fee5ef69.webp differ diff --git a/backend/uploads/images/92136d45-b879-4076-b5f7-f89184b0df13.webp b/backend/uploads/images/92136d45-b879-4076-b5f7-f89184b0df13.webp new file mode 100644 index 0000000..884322e Binary files /dev/null and b/backend/uploads/images/92136d45-b879-4076-b5f7-f89184b0df13.webp differ diff --git a/backend/uploads/images/92866fea-58c7-4034-a897-654c9ab61f23.webp b/backend/uploads/images/92866fea-58c7-4034-a897-654c9ab61f23.webp new file mode 100644 index 0000000..8bb3623 Binary files /dev/null and b/backend/uploads/images/92866fea-58c7-4034-a897-654c9ab61f23.webp differ diff --git a/backend/uploads/images/92ec1f09-843b-4bfb-a885-ab2b91392b3c.webp b/backend/uploads/images/92ec1f09-843b-4bfb-a885-ab2b91392b3c.webp new file mode 100644 index 0000000..d720d69 Binary files /dev/null and b/backend/uploads/images/92ec1f09-843b-4bfb-a885-ab2b91392b3c.webp differ diff --git a/backend/uploads/images/93b8caa7-715e-4bf5-94b4-a8ee68f14015.webp b/backend/uploads/images/93b8caa7-715e-4bf5-94b4-a8ee68f14015.webp new file mode 100644 index 0000000..afc9759 Binary files /dev/null and b/backend/uploads/images/93b8caa7-715e-4bf5-94b4-a8ee68f14015.webp differ diff --git a/backend/uploads/images/93bce766-e00c-4cf6-babb-b1f170cbed1e.webp b/backend/uploads/images/93bce766-e00c-4cf6-babb-b1f170cbed1e.webp new file mode 100644 index 0000000..c995788 Binary files /dev/null and b/backend/uploads/images/93bce766-e00c-4cf6-babb-b1f170cbed1e.webp differ diff --git a/backend/uploads/images/93e12654-feb4-4457-b678-24ce6e468d23.webp b/backend/uploads/images/93e12654-feb4-4457-b678-24ce6e468d23.webp new file mode 100644 index 0000000..9e98c3d Binary files /dev/null and b/backend/uploads/images/93e12654-feb4-4457-b678-24ce6e468d23.webp differ diff --git a/backend/uploads/images/9402268c-e2b6-4462-8778-fb12eb87d23b.webp b/backend/uploads/images/9402268c-e2b6-4462-8778-fb12eb87d23b.webp new file mode 100644 index 0000000..fb55e22 Binary files /dev/null and b/backend/uploads/images/9402268c-e2b6-4462-8778-fb12eb87d23b.webp differ diff --git a/backend/uploads/images/941cb510-ea4a-4d3e-9977-e243717eb4e2.webp b/backend/uploads/images/941cb510-ea4a-4d3e-9977-e243717eb4e2.webp new file mode 100644 index 0000000..7d3d3bb Binary files /dev/null and b/backend/uploads/images/941cb510-ea4a-4d3e-9977-e243717eb4e2.webp differ diff --git a/backend/uploads/images/942677e9-78cc-42d4-8ed6-58c5c916f3bc.webp b/backend/uploads/images/942677e9-78cc-42d4-8ed6-58c5c916f3bc.webp new file mode 100644 index 0000000..898c267 Binary files /dev/null and b/backend/uploads/images/942677e9-78cc-42d4-8ed6-58c5c916f3bc.webp differ diff --git a/backend/uploads/images/9446e74c-3473-4317-b595-4519520f5865.webp b/backend/uploads/images/9446e74c-3473-4317-b595-4519520f5865.webp new file mode 100644 index 0000000..64997dc Binary files /dev/null and b/backend/uploads/images/9446e74c-3473-4317-b595-4519520f5865.webp differ diff --git a/backend/uploads/images/947f714d-a4a9-4890-b2a7-5ce3685fa85a.webp b/backend/uploads/images/947f714d-a4a9-4890-b2a7-5ce3685fa85a.webp new file mode 100644 index 0000000..ffa49a8 Binary files /dev/null and b/backend/uploads/images/947f714d-a4a9-4890-b2a7-5ce3685fa85a.webp differ diff --git a/backend/uploads/images/948632a7-a629-41bc-89f7-40b282765952.webp b/backend/uploads/images/948632a7-a629-41bc-89f7-40b282765952.webp new file mode 100644 index 0000000..df71d61 Binary files /dev/null and b/backend/uploads/images/948632a7-a629-41bc-89f7-40b282765952.webp differ diff --git a/backend/uploads/images/948a413f-b6a0-49f5-a65e-8965030b6366.webp b/backend/uploads/images/948a413f-b6a0-49f5-a65e-8965030b6366.webp new file mode 100644 index 0000000..eb8ae6b Binary files /dev/null and b/backend/uploads/images/948a413f-b6a0-49f5-a65e-8965030b6366.webp differ diff --git a/backend/uploads/images/955154b1-43a4-40ce-9f60-6d10c7c8886a.webp b/backend/uploads/images/955154b1-43a4-40ce-9f60-6d10c7c8886a.webp new file mode 100644 index 0000000..d8b3aa8 Binary files /dev/null and b/backend/uploads/images/955154b1-43a4-40ce-9f60-6d10c7c8886a.webp differ diff --git a/backend/uploads/images/95ab1dc6-7bf1-4269-83c5-2aa9a58bf502.webp b/backend/uploads/images/95ab1dc6-7bf1-4269-83c5-2aa9a58bf502.webp new file mode 100644 index 0000000..056011c Binary files /dev/null and b/backend/uploads/images/95ab1dc6-7bf1-4269-83c5-2aa9a58bf502.webp differ diff --git a/backend/uploads/images/95d94a51-ce06-4c1d-b0f4-b301cc71b912.webp b/backend/uploads/images/95d94a51-ce06-4c1d-b0f4-b301cc71b912.webp new file mode 100644 index 0000000..ee136e1 Binary files /dev/null and b/backend/uploads/images/95d94a51-ce06-4c1d-b0f4-b301cc71b912.webp differ diff --git a/backend/uploads/images/95fabf47-fb02-4ab7-b931-4b29fdbadfb3.webp b/backend/uploads/images/95fabf47-fb02-4ab7-b931-4b29fdbadfb3.webp new file mode 100644 index 0000000..d5b0731 Binary files /dev/null and b/backend/uploads/images/95fabf47-fb02-4ab7-b931-4b29fdbadfb3.webp differ diff --git a/backend/uploads/images/96a5e7f6-334e-4a79-8f7d-032ae5a886ae.webp b/backend/uploads/images/96a5e7f6-334e-4a79-8f7d-032ae5a886ae.webp new file mode 100644 index 0000000..5e854d4 Binary files /dev/null and b/backend/uploads/images/96a5e7f6-334e-4a79-8f7d-032ae5a886ae.webp differ diff --git a/backend/uploads/images/96ec5f7e-8670-409f-977a-f702cbe95ce3.webp b/backend/uploads/images/96ec5f7e-8670-409f-977a-f702cbe95ce3.webp new file mode 100644 index 0000000..87749ba Binary files /dev/null and b/backend/uploads/images/96ec5f7e-8670-409f-977a-f702cbe95ce3.webp differ diff --git a/backend/uploads/images/96f86b85-a8b9-49c3-a910-64b40b76c29d.webp b/backend/uploads/images/96f86b85-a8b9-49c3-a910-64b40b76c29d.webp new file mode 100644 index 0000000..2b6128a Binary files /dev/null and b/backend/uploads/images/96f86b85-a8b9-49c3-a910-64b40b76c29d.webp differ diff --git a/backend/uploads/images/97332ac4-14cb-4356-886e-c0d2f2b5b40b.webp b/backend/uploads/images/97332ac4-14cb-4356-886e-c0d2f2b5b40b.webp new file mode 100644 index 0000000..3c56e7f Binary files /dev/null and b/backend/uploads/images/97332ac4-14cb-4356-886e-c0d2f2b5b40b.webp differ diff --git a/backend/uploads/images/97a78215-d4ed-4cbe-89d7-187ff43c1802.webp b/backend/uploads/images/97a78215-d4ed-4cbe-89d7-187ff43c1802.webp new file mode 100644 index 0000000..97bf6b5 Binary files /dev/null and b/backend/uploads/images/97a78215-d4ed-4cbe-89d7-187ff43c1802.webp differ diff --git a/backend/uploads/images/981419df-dc8c-4f94-8e06-dffa17602c59.webp b/backend/uploads/images/981419df-dc8c-4f94-8e06-dffa17602c59.webp new file mode 100644 index 0000000..d78adcb Binary files /dev/null and b/backend/uploads/images/981419df-dc8c-4f94-8e06-dffa17602c59.webp differ diff --git a/backend/uploads/images/98983e36-4e3f-4930-a088-70980bfdddcb.webp b/backend/uploads/images/98983e36-4e3f-4930-a088-70980bfdddcb.webp new file mode 100644 index 0000000..f6fd275 Binary files /dev/null and b/backend/uploads/images/98983e36-4e3f-4930-a088-70980bfdddcb.webp differ diff --git a/backend/uploads/images/98bd5bc3-e877-46db-a2c5-bc57958894e5.webp b/backend/uploads/images/98bd5bc3-e877-46db-a2c5-bc57958894e5.webp new file mode 100644 index 0000000..9600367 Binary files /dev/null and b/backend/uploads/images/98bd5bc3-e877-46db-a2c5-bc57958894e5.webp differ diff --git a/backend/uploads/images/99501699-2a9f-4318-a419-11f1704c7394.webp b/backend/uploads/images/99501699-2a9f-4318-a419-11f1704c7394.webp new file mode 100644 index 0000000..47450c6 Binary files /dev/null and b/backend/uploads/images/99501699-2a9f-4318-a419-11f1704c7394.webp differ diff --git a/backend/uploads/images/995bcefe-dc74-4ca5-99a1-c75b3105e899.webp b/backend/uploads/images/995bcefe-dc74-4ca5-99a1-c75b3105e899.webp new file mode 100644 index 0000000..7b32ba0 Binary files /dev/null and b/backend/uploads/images/995bcefe-dc74-4ca5-99a1-c75b3105e899.webp differ diff --git a/backend/uploads/images/9a0f59f4-1871-4039-a382-2811218164e9.webp b/backend/uploads/images/9a0f59f4-1871-4039-a382-2811218164e9.webp new file mode 100644 index 0000000..a13d24a Binary files /dev/null and b/backend/uploads/images/9a0f59f4-1871-4039-a382-2811218164e9.webp differ diff --git a/backend/uploads/images/9a38b4cc-98c3-43da-bd96-74b16a0d4db2.webp b/backend/uploads/images/9a38b4cc-98c3-43da-bd96-74b16a0d4db2.webp new file mode 100644 index 0000000..d0c781d Binary files /dev/null and b/backend/uploads/images/9a38b4cc-98c3-43da-bd96-74b16a0d4db2.webp differ diff --git a/backend/uploads/images/9a477f8a-d96f-4ff8-8f26-6ecf63034431.webp b/backend/uploads/images/9a477f8a-d96f-4ff8-8f26-6ecf63034431.webp new file mode 100644 index 0000000..06a68e5 Binary files /dev/null and b/backend/uploads/images/9a477f8a-d96f-4ff8-8f26-6ecf63034431.webp differ diff --git a/backend/uploads/images/9a4ab917-b7cc-4b53-8e1f-8b474ec06597.webp b/backend/uploads/images/9a4ab917-b7cc-4b53-8e1f-8b474ec06597.webp new file mode 100644 index 0000000..ba19927 Binary files /dev/null and b/backend/uploads/images/9a4ab917-b7cc-4b53-8e1f-8b474ec06597.webp differ diff --git a/backend/uploads/images/9abe096a-d55c-4920-a390-a69b6963c8b5.webp b/backend/uploads/images/9abe096a-d55c-4920-a390-a69b6963c8b5.webp new file mode 100644 index 0000000..0cae97e Binary files /dev/null and b/backend/uploads/images/9abe096a-d55c-4920-a390-a69b6963c8b5.webp differ diff --git a/backend/uploads/images/9ac26993-dad7-442b-914c-1cb420fd9eee.webp b/backend/uploads/images/9ac26993-dad7-442b-914c-1cb420fd9eee.webp new file mode 100644 index 0000000..4c50233 Binary files /dev/null and b/backend/uploads/images/9ac26993-dad7-442b-914c-1cb420fd9eee.webp differ diff --git a/backend/uploads/images/9b478840-0bf1-4519-9b52-997fd2bce10f.webp b/backend/uploads/images/9b478840-0bf1-4519-9b52-997fd2bce10f.webp new file mode 100644 index 0000000..e12e23c Binary files /dev/null and b/backend/uploads/images/9b478840-0bf1-4519-9b52-997fd2bce10f.webp differ diff --git a/backend/uploads/images/9b58d85b-c1e0-4a2d-b7a4-0750bf350ecd.webp b/backend/uploads/images/9b58d85b-c1e0-4a2d-b7a4-0750bf350ecd.webp new file mode 100644 index 0000000..6996706 Binary files /dev/null and b/backend/uploads/images/9b58d85b-c1e0-4a2d-b7a4-0750bf350ecd.webp differ diff --git a/backend/uploads/images/9ba01a66-2f9e-4bbc-b2dd-575a650aa8f3.png b/backend/uploads/images/9ba01a66-2f9e-4bbc-b2dd-575a650aa8f3.png new file mode 100644 index 0000000..e20eb4a Binary files /dev/null and b/backend/uploads/images/9ba01a66-2f9e-4bbc-b2dd-575a650aa8f3.png differ diff --git a/backend/uploads/images/9bc169cd-e79d-4a2b-a5ba-170e543599ce.webp b/backend/uploads/images/9bc169cd-e79d-4a2b-a5ba-170e543599ce.webp new file mode 100644 index 0000000..cc4b957 Binary files /dev/null and b/backend/uploads/images/9bc169cd-e79d-4a2b-a5ba-170e543599ce.webp differ diff --git a/backend/uploads/images/9bc8e770-18d1-4de8-b545-c40e47f82198.webp b/backend/uploads/images/9bc8e770-18d1-4de8-b545-c40e47f82198.webp new file mode 100644 index 0000000..0e7ad5e Binary files /dev/null and b/backend/uploads/images/9bc8e770-18d1-4de8-b545-c40e47f82198.webp differ diff --git a/backend/uploads/images/9c871f93-7701-4219-9828-faf07d921f19.webp b/backend/uploads/images/9c871f93-7701-4219-9828-faf07d921f19.webp new file mode 100644 index 0000000..c8884d8 Binary files /dev/null and b/backend/uploads/images/9c871f93-7701-4219-9828-faf07d921f19.webp differ diff --git a/backend/uploads/images/9cc3a81c-3c60-4ed0-b37c-a2a77933291e.webp b/backend/uploads/images/9cc3a81c-3c60-4ed0-b37c-a2a77933291e.webp new file mode 100644 index 0000000..3873d26 Binary files /dev/null and b/backend/uploads/images/9cc3a81c-3c60-4ed0-b37c-a2a77933291e.webp differ diff --git a/backend/uploads/images/9cd0db1d-930d-4d06-8591-f2238a3fb339.webp b/backend/uploads/images/9cd0db1d-930d-4d06-8591-f2238a3fb339.webp new file mode 100644 index 0000000..71652b1 Binary files /dev/null and b/backend/uploads/images/9cd0db1d-930d-4d06-8591-f2238a3fb339.webp differ diff --git a/backend/uploads/images/9d528585-17b8-4087-9fcd-e4c50ee185ef.webp b/backend/uploads/images/9d528585-17b8-4087-9fcd-e4c50ee185ef.webp new file mode 100644 index 0000000..d6d016e Binary files /dev/null and b/backend/uploads/images/9d528585-17b8-4087-9fcd-e4c50ee185ef.webp differ diff --git a/backend/uploads/images/9ded9eb0-68b8-4f78-9648-cb721b15922a.webp b/backend/uploads/images/9ded9eb0-68b8-4f78-9648-cb721b15922a.webp new file mode 100644 index 0000000..35497cf Binary files /dev/null and b/backend/uploads/images/9ded9eb0-68b8-4f78-9648-cb721b15922a.webp differ diff --git a/backend/uploads/images/9e11bd76-c9c8-4440-9733-5209a734f4ab.webp b/backend/uploads/images/9e11bd76-c9c8-4440-9733-5209a734f4ab.webp new file mode 100644 index 0000000..33ce29d Binary files /dev/null and b/backend/uploads/images/9e11bd76-c9c8-4440-9733-5209a734f4ab.webp differ diff --git a/backend/uploads/images/9e40e6ef-0dc7-42df-b65b-de9aa47ba7dd.webp b/backend/uploads/images/9e40e6ef-0dc7-42df-b65b-de9aa47ba7dd.webp new file mode 100644 index 0000000..b9aede2 Binary files /dev/null and b/backend/uploads/images/9e40e6ef-0dc7-42df-b65b-de9aa47ba7dd.webp differ diff --git a/backend/uploads/images/9e435b7a-c4f6-4e4a-a19b-01cc99c8bb04.webp b/backend/uploads/images/9e435b7a-c4f6-4e4a-a19b-01cc99c8bb04.webp new file mode 100644 index 0000000..60d7b0f Binary files /dev/null and b/backend/uploads/images/9e435b7a-c4f6-4e4a-a19b-01cc99c8bb04.webp differ diff --git a/backend/uploads/images/9e9019d9-cb79-465a-87eb-3e1119e5acbc.webp b/backend/uploads/images/9e9019d9-cb79-465a-87eb-3e1119e5acbc.webp new file mode 100644 index 0000000..25cd3f6 Binary files /dev/null and b/backend/uploads/images/9e9019d9-cb79-465a-87eb-3e1119e5acbc.webp differ diff --git a/backend/uploads/images/9ebe80c6-2bee-4e44-8e65-dc526449201b.webp b/backend/uploads/images/9ebe80c6-2bee-4e44-8e65-dc526449201b.webp new file mode 100644 index 0000000..60f1595 Binary files /dev/null and b/backend/uploads/images/9ebe80c6-2bee-4e44-8e65-dc526449201b.webp differ diff --git a/backend/uploads/images/9fcefde3-4e47-4019-9bd8-3337770fc7e6.webp b/backend/uploads/images/9fcefde3-4e47-4019-9bd8-3337770fc7e6.webp new file mode 100644 index 0000000..de86665 Binary files /dev/null and b/backend/uploads/images/9fcefde3-4e47-4019-9bd8-3337770fc7e6.webp differ diff --git a/backend/uploads/images/a002ef36-4b08-4d9d-b7a8-c660c8f99d16.webp b/backend/uploads/images/a002ef36-4b08-4d9d-b7a8-c660c8f99d16.webp new file mode 100644 index 0000000..50413e1 Binary files /dev/null and b/backend/uploads/images/a002ef36-4b08-4d9d-b7a8-c660c8f99d16.webp differ diff --git a/backend/uploads/images/a05ffee6-1b7d-49ec-986e-b0cd7356ae1b.webp b/backend/uploads/images/a05ffee6-1b7d-49ec-986e-b0cd7356ae1b.webp new file mode 100644 index 0000000..1482eb9 Binary files /dev/null and b/backend/uploads/images/a05ffee6-1b7d-49ec-986e-b0cd7356ae1b.webp differ diff --git a/backend/uploads/images/a0b8acc8-d472-4f15-93d4-aab1e6be680a.webp b/backend/uploads/images/a0b8acc8-d472-4f15-93d4-aab1e6be680a.webp new file mode 100644 index 0000000..bebee54 Binary files /dev/null and b/backend/uploads/images/a0b8acc8-d472-4f15-93d4-aab1e6be680a.webp differ diff --git a/backend/uploads/images/a1206dd3-96a3-4c00-a585-3820e4011454.webp b/backend/uploads/images/a1206dd3-96a3-4c00-a585-3820e4011454.webp new file mode 100644 index 0000000..e57906e Binary files /dev/null and b/backend/uploads/images/a1206dd3-96a3-4c00-a585-3820e4011454.webp differ diff --git a/backend/uploads/images/a14cc77d-0f13-491b-9559-bc16c6af2492.webp b/backend/uploads/images/a14cc77d-0f13-491b-9559-bc16c6af2492.webp new file mode 100644 index 0000000..87dd88f Binary files /dev/null and b/backend/uploads/images/a14cc77d-0f13-491b-9559-bc16c6af2492.webp differ diff --git a/backend/uploads/images/a157c329-5a8e-47c6-a9a2-0777f48541f7.webp b/backend/uploads/images/a157c329-5a8e-47c6-a9a2-0777f48541f7.webp new file mode 100644 index 0000000..d2db304 Binary files /dev/null and b/backend/uploads/images/a157c329-5a8e-47c6-a9a2-0777f48541f7.webp differ diff --git a/backend/uploads/images/a1744b52-e194-4097-ad17-d89feca7a627.webp b/backend/uploads/images/a1744b52-e194-4097-ad17-d89feca7a627.webp new file mode 100644 index 0000000..2d39b61 Binary files /dev/null and b/backend/uploads/images/a1744b52-e194-4097-ad17-d89feca7a627.webp differ diff --git a/backend/uploads/images/a1fce1cf-0ddb-4af1-983d-24a8a1b2fe7c.webp b/backend/uploads/images/a1fce1cf-0ddb-4af1-983d-24a8a1b2fe7c.webp new file mode 100644 index 0000000..0807569 Binary files /dev/null and b/backend/uploads/images/a1fce1cf-0ddb-4af1-983d-24a8a1b2fe7c.webp differ diff --git a/backend/uploads/images/a2a90f6b-3490-48fe-9ed1-bf87b32e7e9c.webp b/backend/uploads/images/a2a90f6b-3490-48fe-9ed1-bf87b32e7e9c.webp new file mode 100644 index 0000000..08c76a1 Binary files /dev/null and b/backend/uploads/images/a2a90f6b-3490-48fe-9ed1-bf87b32e7e9c.webp differ diff --git a/backend/uploads/images/a30fe0ff-72d8-4f9a-80d1-f2c88be1766c.webp b/backend/uploads/images/a30fe0ff-72d8-4f9a-80d1-f2c88be1766c.webp new file mode 100644 index 0000000..cd5dc41 Binary files /dev/null and b/backend/uploads/images/a30fe0ff-72d8-4f9a-80d1-f2c88be1766c.webp differ diff --git a/backend/uploads/images/a355f978-10b9-4df9-85d8-e499ca7505dd.webp b/backend/uploads/images/a355f978-10b9-4df9-85d8-e499ca7505dd.webp new file mode 100644 index 0000000..7e0fdca Binary files /dev/null and b/backend/uploads/images/a355f978-10b9-4df9-85d8-e499ca7505dd.webp differ diff --git a/backend/uploads/images/a3937db9-ec58-416d-841b-cd0f1cfc81ff.webp b/backend/uploads/images/a3937db9-ec58-416d-841b-cd0f1cfc81ff.webp new file mode 100644 index 0000000..11ee2a5 Binary files /dev/null and b/backend/uploads/images/a3937db9-ec58-416d-841b-cd0f1cfc81ff.webp differ diff --git a/backend/uploads/images/a3fa07df-9fc5-44e5-8c39-eb26e48a622e.webp b/backend/uploads/images/a3fa07df-9fc5-44e5-8c39-eb26e48a622e.webp new file mode 100644 index 0000000..f6bb4d1 Binary files /dev/null and b/backend/uploads/images/a3fa07df-9fc5-44e5-8c39-eb26e48a622e.webp differ diff --git a/backend/uploads/images/a4a354d8-6858-41c2-8395-85d2009f1f1c.webp b/backend/uploads/images/a4a354d8-6858-41c2-8395-85d2009f1f1c.webp new file mode 100644 index 0000000..69aeebc Binary files /dev/null and b/backend/uploads/images/a4a354d8-6858-41c2-8395-85d2009f1f1c.webp differ diff --git a/backend/uploads/images/a4fe5975-a6aa-4574-bd6d-9a98c5f3a128.webp b/backend/uploads/images/a4fe5975-a6aa-4574-bd6d-9a98c5f3a128.webp new file mode 100644 index 0000000..1403df8 Binary files /dev/null and b/backend/uploads/images/a4fe5975-a6aa-4574-bd6d-9a98c5f3a128.webp differ diff --git a/backend/uploads/images/a526f638-15f2-4b04-912c-e606b11a663b.webp b/backend/uploads/images/a526f638-15f2-4b04-912c-e606b11a663b.webp new file mode 100644 index 0000000..3ec78ad Binary files /dev/null and b/backend/uploads/images/a526f638-15f2-4b04-912c-e606b11a663b.webp differ diff --git a/backend/uploads/images/a5e8a446-819b-4b6a-93b1-dcf3f0ab6613.webp b/backend/uploads/images/a5e8a446-819b-4b6a-93b1-dcf3f0ab6613.webp new file mode 100644 index 0000000..7bb09e7 Binary files /dev/null and b/backend/uploads/images/a5e8a446-819b-4b6a-93b1-dcf3f0ab6613.webp differ diff --git a/backend/uploads/images/a667c2eb-a6a4-44e5-bd6c-11f169dec203.webp b/backend/uploads/images/a667c2eb-a6a4-44e5-bd6c-11f169dec203.webp new file mode 100644 index 0000000..8e71098 Binary files /dev/null and b/backend/uploads/images/a667c2eb-a6a4-44e5-bd6c-11f169dec203.webp differ diff --git a/backend/uploads/images/a68b5ef8-f3c8-485e-854d-f6f1e63a7249.webp b/backend/uploads/images/a68b5ef8-f3c8-485e-854d-f6f1e63a7249.webp new file mode 100644 index 0000000..b96c1de Binary files /dev/null and b/backend/uploads/images/a68b5ef8-f3c8-485e-854d-f6f1e63a7249.webp differ diff --git a/backend/uploads/images/a6cb1038-ef4c-4ec0-a651-db8824fcde60.webp b/backend/uploads/images/a6cb1038-ef4c-4ec0-a651-db8824fcde60.webp new file mode 100644 index 0000000..7cd12a4 Binary files /dev/null and b/backend/uploads/images/a6cb1038-ef4c-4ec0-a651-db8824fcde60.webp differ diff --git a/backend/uploads/images/a75f452d-bd09-44c2-87e9-fb46927a89fa.webp b/backend/uploads/images/a75f452d-bd09-44c2-87e9-fb46927a89fa.webp new file mode 100644 index 0000000..9f1ce45 Binary files /dev/null and b/backend/uploads/images/a75f452d-bd09-44c2-87e9-fb46927a89fa.webp differ diff --git a/backend/uploads/images/a79e48dd-670a-4adc-aa6f-676593088327.webp b/backend/uploads/images/a79e48dd-670a-4adc-aa6f-676593088327.webp new file mode 100644 index 0000000..7825f53 Binary files /dev/null and b/backend/uploads/images/a79e48dd-670a-4adc-aa6f-676593088327.webp differ diff --git a/backend/uploads/images/a79fdd0f-14d8-403d-9567-42f7097d5ff5.webp b/backend/uploads/images/a79fdd0f-14d8-403d-9567-42f7097d5ff5.webp new file mode 100644 index 0000000..b3e6c29 Binary files /dev/null and b/backend/uploads/images/a79fdd0f-14d8-403d-9567-42f7097d5ff5.webp differ diff --git a/backend/uploads/images/a7a38775-cea1-4031-963b-ad3033a4b53a.webp b/backend/uploads/images/a7a38775-cea1-4031-963b-ad3033a4b53a.webp new file mode 100644 index 0000000..67ab2ac Binary files /dev/null and b/backend/uploads/images/a7a38775-cea1-4031-963b-ad3033a4b53a.webp differ diff --git a/backend/uploads/images/a8771263-31cc-4add-a7df-10522e408c9c.webp b/backend/uploads/images/a8771263-31cc-4add-a7df-10522e408c9c.webp new file mode 100644 index 0000000..2c2d287 Binary files /dev/null and b/backend/uploads/images/a8771263-31cc-4add-a7df-10522e408c9c.webp differ diff --git a/backend/uploads/images/a90093c2-b25a-49a2-a01c-be2f778f2aa2.webp b/backend/uploads/images/a90093c2-b25a-49a2-a01c-be2f778f2aa2.webp new file mode 100644 index 0000000..3eba08c Binary files /dev/null and b/backend/uploads/images/a90093c2-b25a-49a2-a01c-be2f778f2aa2.webp differ diff --git a/backend/uploads/images/aa1279e7-9259-4700-a552-e659e49e1aa6.webp b/backend/uploads/images/aa1279e7-9259-4700-a552-e659e49e1aa6.webp new file mode 100644 index 0000000..c8eea5f Binary files /dev/null and b/backend/uploads/images/aa1279e7-9259-4700-a552-e659e49e1aa6.webp differ diff --git a/backend/uploads/images/aa6bb4fb-bdd5-49ba-8dad-77aa44feae20.webp b/backend/uploads/images/aa6bb4fb-bdd5-49ba-8dad-77aa44feae20.webp new file mode 100644 index 0000000..38203d1 Binary files /dev/null and b/backend/uploads/images/aa6bb4fb-bdd5-49ba-8dad-77aa44feae20.webp differ diff --git a/backend/uploads/images/aa980345-fa8a-41db-a517-6f908285ece2.webp b/backend/uploads/images/aa980345-fa8a-41db-a517-6f908285ece2.webp new file mode 100644 index 0000000..c7abb0c Binary files /dev/null and b/backend/uploads/images/aa980345-fa8a-41db-a517-6f908285ece2.webp differ diff --git a/backend/uploads/images/aa9b4afc-f579-4a41-a72f-d4a036936c20.webp b/backend/uploads/images/aa9b4afc-f579-4a41-a72f-d4a036936c20.webp new file mode 100644 index 0000000..7ad1c78 Binary files /dev/null and b/backend/uploads/images/aa9b4afc-f579-4a41-a72f-d4a036936c20.webp differ diff --git a/backend/uploads/images/aace30e6-3a4b-4d33-973a-91a5026ce7a9.webp b/backend/uploads/images/aace30e6-3a4b-4d33-973a-91a5026ce7a9.webp new file mode 100644 index 0000000..91124de Binary files /dev/null and b/backend/uploads/images/aace30e6-3a4b-4d33-973a-91a5026ce7a9.webp differ diff --git a/backend/uploads/images/ab44fe71-16b1-40a5-981d-0228a8b14e8c.webp b/backend/uploads/images/ab44fe71-16b1-40a5-981d-0228a8b14e8c.webp new file mode 100644 index 0000000..af0046c Binary files /dev/null and b/backend/uploads/images/ab44fe71-16b1-40a5-981d-0228a8b14e8c.webp differ diff --git a/backend/uploads/images/ab5e86ee-e045-414c-beaf-07c6c7f7e3bc.webp b/backend/uploads/images/ab5e86ee-e045-414c-beaf-07c6c7f7e3bc.webp new file mode 100644 index 0000000..a9c278b Binary files /dev/null and b/backend/uploads/images/ab5e86ee-e045-414c-beaf-07c6c7f7e3bc.webp differ diff --git a/backend/uploads/images/ab87dc30-e6b8-4303-bb07-310582aca2de.webp b/backend/uploads/images/ab87dc30-e6b8-4303-bb07-310582aca2de.webp new file mode 100644 index 0000000..36a733c Binary files /dev/null and b/backend/uploads/images/ab87dc30-e6b8-4303-bb07-310582aca2de.webp differ diff --git a/backend/uploads/images/abed1cff-32c5-4ebf-98ba-a43598074737.webp b/backend/uploads/images/abed1cff-32c5-4ebf-98ba-a43598074737.webp new file mode 100644 index 0000000..8dc9f94 Binary files /dev/null and b/backend/uploads/images/abed1cff-32c5-4ebf-98ba-a43598074737.webp differ diff --git a/backend/uploads/images/ac26ec18-8a2f-4186-b1d4-47fef9964fe7.webp b/backend/uploads/images/ac26ec18-8a2f-4186-b1d4-47fef9964fe7.webp new file mode 100644 index 0000000..edf162c Binary files /dev/null and b/backend/uploads/images/ac26ec18-8a2f-4186-b1d4-47fef9964fe7.webp differ diff --git a/backend/uploads/images/ac445ccb-794f-4352-aea3-42ffd474c74c.webp b/backend/uploads/images/ac445ccb-794f-4352-aea3-42ffd474c74c.webp new file mode 100644 index 0000000..a922098 Binary files /dev/null and b/backend/uploads/images/ac445ccb-794f-4352-aea3-42ffd474c74c.webp differ diff --git a/backend/uploads/images/acd0cb90-102f-4cff-ac06-507f64f27755.webp b/backend/uploads/images/acd0cb90-102f-4cff-ac06-507f64f27755.webp new file mode 100644 index 0000000..c703a9b Binary files /dev/null and b/backend/uploads/images/acd0cb90-102f-4cff-ac06-507f64f27755.webp differ diff --git a/backend/uploads/images/ad7550b4-7586-4307-89bd-415be24dec4f.webp b/backend/uploads/images/ad7550b4-7586-4307-89bd-415be24dec4f.webp new file mode 100644 index 0000000..6d3407f Binary files /dev/null and b/backend/uploads/images/ad7550b4-7586-4307-89bd-415be24dec4f.webp differ diff --git a/backend/uploads/images/adcba896-9c62-459b-9d6c-d4649c729130.webp b/backend/uploads/images/adcba896-9c62-459b-9d6c-d4649c729130.webp new file mode 100644 index 0000000..1b0c8be Binary files /dev/null and b/backend/uploads/images/adcba896-9c62-459b-9d6c-d4649c729130.webp differ diff --git a/backend/uploads/images/adfe1a64-7e29-4838-b351-86ccd67d5be8.webp b/backend/uploads/images/adfe1a64-7e29-4838-b351-86ccd67d5be8.webp new file mode 100644 index 0000000..63e7aa2 Binary files /dev/null and b/backend/uploads/images/adfe1a64-7e29-4838-b351-86ccd67d5be8.webp differ diff --git a/backend/uploads/images/ae180830-7ab4-46b1-9dcf-e46e722afb85.webp b/backend/uploads/images/ae180830-7ab4-46b1-9dcf-e46e722afb85.webp new file mode 100644 index 0000000..5a13790 Binary files /dev/null and b/backend/uploads/images/ae180830-7ab4-46b1-9dcf-e46e722afb85.webp differ diff --git a/backend/uploads/images/ae3f5f59-5000-479b-bd87-3832f6e9821f.webp b/backend/uploads/images/ae3f5f59-5000-479b-bd87-3832f6e9821f.webp new file mode 100644 index 0000000..a5ff5c5 Binary files /dev/null and b/backend/uploads/images/ae3f5f59-5000-479b-bd87-3832f6e9821f.webp differ diff --git a/backend/uploads/images/ae4d5f6a-560d-4fa8-9173-4aa88864c0f4.webp b/backend/uploads/images/ae4d5f6a-560d-4fa8-9173-4aa88864c0f4.webp new file mode 100644 index 0000000..bcc5510 Binary files /dev/null and b/backend/uploads/images/ae4d5f6a-560d-4fa8-9173-4aa88864c0f4.webp differ diff --git a/backend/uploads/images/ae7b6a96-7090-4ddf-b950-cb5832c9492a.webp b/backend/uploads/images/ae7b6a96-7090-4ddf-b950-cb5832c9492a.webp new file mode 100644 index 0000000..5e6fbfa Binary files /dev/null and b/backend/uploads/images/ae7b6a96-7090-4ddf-b950-cb5832c9492a.webp differ diff --git a/backend/uploads/images/aea3c789-c2a8-4a21-a692-0d57a8e0aff6.webp b/backend/uploads/images/aea3c789-c2a8-4a21-a692-0d57a8e0aff6.webp new file mode 100644 index 0000000..839620f Binary files /dev/null and b/backend/uploads/images/aea3c789-c2a8-4a21-a692-0d57a8e0aff6.webp differ diff --git a/backend/uploads/images/af7003f0-4cc1-41ca-932f-d0bc4dd21d24.webp b/backend/uploads/images/af7003f0-4cc1-41ca-932f-d0bc4dd21d24.webp new file mode 100644 index 0000000..e04cfd7 Binary files /dev/null and b/backend/uploads/images/af7003f0-4cc1-41ca-932f-d0bc4dd21d24.webp differ diff --git a/backend/uploads/images/af913b2f-72af-495b-8a16-4c8e0e6253a6.webp b/backend/uploads/images/af913b2f-72af-495b-8a16-4c8e0e6253a6.webp new file mode 100644 index 0000000..2018da0 Binary files /dev/null and b/backend/uploads/images/af913b2f-72af-495b-8a16-4c8e0e6253a6.webp differ diff --git a/backend/uploads/images/afd9008b-b616-4a97-887f-a2e4908c2f5d.webp b/backend/uploads/images/afd9008b-b616-4a97-887f-a2e4908c2f5d.webp new file mode 100644 index 0000000..ef5802b Binary files /dev/null and b/backend/uploads/images/afd9008b-b616-4a97-887f-a2e4908c2f5d.webp differ diff --git a/backend/uploads/images/b12669a3-1501-47a9-8340-d44f3f6e7999.webp b/backend/uploads/images/b12669a3-1501-47a9-8340-d44f3f6e7999.webp new file mode 100644 index 0000000..19bb000 Binary files /dev/null and b/backend/uploads/images/b12669a3-1501-47a9-8340-d44f3f6e7999.webp differ diff --git a/backend/uploads/images/b213bcc9-dd74-4b2b-8417-8ea1026ba5bd.webp b/backend/uploads/images/b213bcc9-dd74-4b2b-8417-8ea1026ba5bd.webp new file mode 100644 index 0000000..19b1369 Binary files /dev/null and b/backend/uploads/images/b213bcc9-dd74-4b2b-8417-8ea1026ba5bd.webp differ diff --git a/backend/uploads/images/b5bab5c5-5bac-4ba8-9c6e-868fd9c5c3f8.webp b/backend/uploads/images/b5bab5c5-5bac-4ba8-9c6e-868fd9c5c3f8.webp new file mode 100644 index 0000000..6e601b6 Binary files /dev/null and b/backend/uploads/images/b5bab5c5-5bac-4ba8-9c6e-868fd9c5c3f8.webp differ diff --git a/backend/uploads/images/b608ff28-a5a4-4e11-a220-03bd12e0eca2.webp b/backend/uploads/images/b608ff28-a5a4-4e11-a220-03bd12e0eca2.webp new file mode 100644 index 0000000..ba57c84 Binary files /dev/null and b/backend/uploads/images/b608ff28-a5a4-4e11-a220-03bd12e0eca2.webp differ diff --git a/backend/uploads/images/b690d263-273d-4962-8cd3-330821c4e17b.webp b/backend/uploads/images/b690d263-273d-4962-8cd3-330821c4e17b.webp new file mode 100644 index 0000000..d4342e7 Binary files /dev/null and b/backend/uploads/images/b690d263-273d-4962-8cd3-330821c4e17b.webp differ diff --git a/backend/uploads/images/b6ec0572-9389-451e-a81d-a2493647f8d4.webp b/backend/uploads/images/b6ec0572-9389-451e-a81d-a2493647f8d4.webp new file mode 100644 index 0000000..160d9e5 Binary files /dev/null and b/backend/uploads/images/b6ec0572-9389-451e-a81d-a2493647f8d4.webp differ diff --git a/backend/uploads/images/b72a66f0-cfa6-444b-98fc-37082cba96b9.webp b/backend/uploads/images/b72a66f0-cfa6-444b-98fc-37082cba96b9.webp new file mode 100644 index 0000000..0c359fb Binary files /dev/null and b/backend/uploads/images/b72a66f0-cfa6-444b-98fc-37082cba96b9.webp differ diff --git a/backend/uploads/images/b77e6ac3-1660-4ea9-8ac5-488a3c285117.webp b/backend/uploads/images/b77e6ac3-1660-4ea9-8ac5-488a3c285117.webp new file mode 100644 index 0000000..f50eeca Binary files /dev/null and b/backend/uploads/images/b77e6ac3-1660-4ea9-8ac5-488a3c285117.webp differ diff --git a/backend/uploads/images/b79ed0aa-ccd1-4468-928d-8e91ca794cfa.webp b/backend/uploads/images/b79ed0aa-ccd1-4468-928d-8e91ca794cfa.webp new file mode 100644 index 0000000..84836b9 Binary files /dev/null and b/backend/uploads/images/b79ed0aa-ccd1-4468-928d-8e91ca794cfa.webp differ diff --git a/backend/uploads/images/b7a95a2a-0a9e-4eb2-819b-e132292a208f.webp b/backend/uploads/images/b7a95a2a-0a9e-4eb2-819b-e132292a208f.webp new file mode 100644 index 0000000..a159a7e Binary files /dev/null and b/backend/uploads/images/b7a95a2a-0a9e-4eb2-819b-e132292a208f.webp differ diff --git a/backend/uploads/images/b7fc0ded-972f-4799-8a50-77f508274a15.webp b/backend/uploads/images/b7fc0ded-972f-4799-8a50-77f508274a15.webp new file mode 100644 index 0000000..6dc5052 Binary files /dev/null and b/backend/uploads/images/b7fc0ded-972f-4799-8a50-77f508274a15.webp differ diff --git a/backend/uploads/images/b84f3a1b-11ad-4e78-8846-4733911efc2c.webp b/backend/uploads/images/b84f3a1b-11ad-4e78-8846-4733911efc2c.webp new file mode 100644 index 0000000..32efffe Binary files /dev/null and b/backend/uploads/images/b84f3a1b-11ad-4e78-8846-4733911efc2c.webp differ diff --git a/backend/uploads/images/b8dccfc8-c8a6-4b55-997b-6ab92e347b49.webp b/backend/uploads/images/b8dccfc8-c8a6-4b55-997b-6ab92e347b49.webp new file mode 100644 index 0000000..be4afd0 Binary files /dev/null and b/backend/uploads/images/b8dccfc8-c8a6-4b55-997b-6ab92e347b49.webp differ diff --git a/backend/uploads/images/b8e59149-fa91-46e5-9e5f-5b94930ac950.webp b/backend/uploads/images/b8e59149-fa91-46e5-9e5f-5b94930ac950.webp new file mode 100644 index 0000000..2537150 Binary files /dev/null and b/backend/uploads/images/b8e59149-fa91-46e5-9e5f-5b94930ac950.webp differ diff --git a/backend/uploads/images/b920832b-c23c-4d86-aaac-50ffb6dfbae8.webp b/backend/uploads/images/b920832b-c23c-4d86-aaac-50ffb6dfbae8.webp new file mode 100644 index 0000000..db21c86 Binary files /dev/null and b/backend/uploads/images/b920832b-c23c-4d86-aaac-50ffb6dfbae8.webp differ diff --git a/backend/uploads/images/b94005ba-2565-4b2f-8d54-73aac94ddf71.webp b/backend/uploads/images/b94005ba-2565-4b2f-8d54-73aac94ddf71.webp new file mode 100644 index 0000000..011cdf9 Binary files /dev/null and b/backend/uploads/images/b94005ba-2565-4b2f-8d54-73aac94ddf71.webp differ diff --git a/backend/uploads/images/b98a4899-a19f-42d7-8616-708cf395859f.webp b/backend/uploads/images/b98a4899-a19f-42d7-8616-708cf395859f.webp new file mode 100644 index 0000000..4922d49 Binary files /dev/null and b/backend/uploads/images/b98a4899-a19f-42d7-8616-708cf395859f.webp differ diff --git a/backend/uploads/images/b9b73ca0-424b-490a-ad70-b55de6efdd6d.webp b/backend/uploads/images/b9b73ca0-424b-490a-ad70-b55de6efdd6d.webp new file mode 100644 index 0000000..bed0ae3 Binary files /dev/null and b/backend/uploads/images/b9b73ca0-424b-490a-ad70-b55de6efdd6d.webp differ diff --git a/backend/uploads/images/b9ba9a46-c37e-41a8-a14e-7ef10ec6836a.webp b/backend/uploads/images/b9ba9a46-c37e-41a8-a14e-7ef10ec6836a.webp new file mode 100644 index 0000000..792a7bc Binary files /dev/null and b/backend/uploads/images/b9ba9a46-c37e-41a8-a14e-7ef10ec6836a.webp differ diff --git a/backend/uploads/images/ba261e3f-9096-4dd3-afae-a0b554c47517.webp b/backend/uploads/images/ba261e3f-9096-4dd3-afae-a0b554c47517.webp new file mode 100644 index 0000000..f26a7bd Binary files /dev/null and b/backend/uploads/images/ba261e3f-9096-4dd3-afae-a0b554c47517.webp differ diff --git a/backend/uploads/images/ba58be9e-4db5-4d4f-b4e3-eb27800256ce.webp b/backend/uploads/images/ba58be9e-4db5-4d4f-b4e3-eb27800256ce.webp new file mode 100644 index 0000000..9443b72 Binary files /dev/null and b/backend/uploads/images/ba58be9e-4db5-4d4f-b4e3-eb27800256ce.webp differ diff --git a/backend/uploads/images/baaf8fbf-7795-47cb-a815-1ad8edd0fe43.webp b/backend/uploads/images/baaf8fbf-7795-47cb-a815-1ad8edd0fe43.webp new file mode 100644 index 0000000..0c8a9e3 Binary files /dev/null and b/backend/uploads/images/baaf8fbf-7795-47cb-a815-1ad8edd0fe43.webp differ diff --git a/backend/uploads/images/bb61d25d-e2d7-4a00-b880-e400d7ffdb6f.webp b/backend/uploads/images/bb61d25d-e2d7-4a00-b880-e400d7ffdb6f.webp new file mode 100644 index 0000000..84b48bf Binary files /dev/null and b/backend/uploads/images/bb61d25d-e2d7-4a00-b880-e400d7ffdb6f.webp differ diff --git a/backend/uploads/images/bb8ca167-13e8-4d37-945c-9babdd254c19.webp b/backend/uploads/images/bb8ca167-13e8-4d37-945c-9babdd254c19.webp new file mode 100644 index 0000000..8c88d16 Binary files /dev/null and b/backend/uploads/images/bb8ca167-13e8-4d37-945c-9babdd254c19.webp differ diff --git a/backend/uploads/images/bbce6ba8-19e3-4d02-a8b2-6b46c9e0fb90.webp b/backend/uploads/images/bbce6ba8-19e3-4d02-a8b2-6b46c9e0fb90.webp new file mode 100644 index 0000000..e9a2bd8 Binary files /dev/null and b/backend/uploads/images/bbce6ba8-19e3-4d02-a8b2-6b46c9e0fb90.webp differ diff --git a/backend/uploads/images/bbd2f6bc-d199-4f3c-8010-8de57c751257.webp b/backend/uploads/images/bbd2f6bc-d199-4f3c-8010-8de57c751257.webp new file mode 100644 index 0000000..5b3d57f Binary files /dev/null and b/backend/uploads/images/bbd2f6bc-d199-4f3c-8010-8de57c751257.webp differ diff --git a/backend/uploads/images/bc1890eb-e64d-4e77-af5c-960707d8b3b1.webp b/backend/uploads/images/bc1890eb-e64d-4e77-af5c-960707d8b3b1.webp new file mode 100644 index 0000000..af90348 Binary files /dev/null and b/backend/uploads/images/bc1890eb-e64d-4e77-af5c-960707d8b3b1.webp differ diff --git a/backend/uploads/images/bc3e51d8-37d0-4135-9af6-553286908f87.webp b/backend/uploads/images/bc3e51d8-37d0-4135-9af6-553286908f87.webp new file mode 100644 index 0000000..17f6936 Binary files /dev/null and b/backend/uploads/images/bc3e51d8-37d0-4135-9af6-553286908f87.webp differ diff --git a/backend/uploads/images/bc5e8c2e-6a98-4d08-a832-7d9ad18a856e.webp b/backend/uploads/images/bc5e8c2e-6a98-4d08-a832-7d9ad18a856e.webp new file mode 100644 index 0000000..f1a97e4 Binary files /dev/null and b/backend/uploads/images/bc5e8c2e-6a98-4d08-a832-7d9ad18a856e.webp differ diff --git a/backend/uploads/images/bcea736a-1c42-4ddc-b095-7ef1668b4f5a.webp b/backend/uploads/images/bcea736a-1c42-4ddc-b095-7ef1668b4f5a.webp new file mode 100644 index 0000000..3f80675 Binary files /dev/null and b/backend/uploads/images/bcea736a-1c42-4ddc-b095-7ef1668b4f5a.webp differ diff --git a/backend/uploads/images/bda923df-5d81-423a-8aef-d0ea2f5ca5a5.webp b/backend/uploads/images/bda923df-5d81-423a-8aef-d0ea2f5ca5a5.webp new file mode 100644 index 0000000..8bcfea7 Binary files /dev/null and b/backend/uploads/images/bda923df-5d81-423a-8aef-d0ea2f5ca5a5.webp differ diff --git a/backend/uploads/images/bdb14e7e-976f-417b-b4c7-cdb84b456919.webp b/backend/uploads/images/bdb14e7e-976f-417b-b4c7-cdb84b456919.webp new file mode 100644 index 0000000..2659850 Binary files /dev/null and b/backend/uploads/images/bdb14e7e-976f-417b-b4c7-cdb84b456919.webp differ diff --git a/backend/uploads/images/bdef805f-fc76-4c3e-beaf-3cea1c0e67a5.webp b/backend/uploads/images/bdef805f-fc76-4c3e-beaf-3cea1c0e67a5.webp new file mode 100644 index 0000000..7ffb5a8 Binary files /dev/null and b/backend/uploads/images/bdef805f-fc76-4c3e-beaf-3cea1c0e67a5.webp differ diff --git a/backend/uploads/images/be3a9aad-a49d-4bd5-a16a-f2f2de723496.webp b/backend/uploads/images/be3a9aad-a49d-4bd5-a16a-f2f2de723496.webp new file mode 100644 index 0000000..c345654 Binary files /dev/null and b/backend/uploads/images/be3a9aad-a49d-4bd5-a16a-f2f2de723496.webp differ diff --git a/backend/uploads/images/be4c689a-fbea-415b-9b66-8607a8873871.webp b/backend/uploads/images/be4c689a-fbea-415b-9b66-8607a8873871.webp new file mode 100644 index 0000000..99fcbad Binary files /dev/null and b/backend/uploads/images/be4c689a-fbea-415b-9b66-8607a8873871.webp differ diff --git a/backend/uploads/images/be4f33ed-ccca-44e2-a7b7-93374d7a3484.webp b/backend/uploads/images/be4f33ed-ccca-44e2-a7b7-93374d7a3484.webp new file mode 100644 index 0000000..288ed01 Binary files /dev/null and b/backend/uploads/images/be4f33ed-ccca-44e2-a7b7-93374d7a3484.webp differ diff --git a/backend/uploads/images/bf54c653-8237-48c4-83d1-2dd340a8f416.webp b/backend/uploads/images/bf54c653-8237-48c4-83d1-2dd340a8f416.webp new file mode 100644 index 0000000..224ebbf Binary files /dev/null and b/backend/uploads/images/bf54c653-8237-48c4-83d1-2dd340a8f416.webp differ diff --git a/backend/uploads/images/bf686ef8-1c9e-443c-a21f-15becd2034c2.webp b/backend/uploads/images/bf686ef8-1c9e-443c-a21f-15becd2034c2.webp new file mode 100644 index 0000000..a350068 Binary files /dev/null and b/backend/uploads/images/bf686ef8-1c9e-443c-a21f-15becd2034c2.webp differ diff --git a/backend/uploads/images/bfddbb93-0a45-4e36-aacc-bf0dfbee7451.webp b/backend/uploads/images/bfddbb93-0a45-4e36-aacc-bf0dfbee7451.webp new file mode 100644 index 0000000..2705fe3 Binary files /dev/null and b/backend/uploads/images/bfddbb93-0a45-4e36-aacc-bf0dfbee7451.webp differ diff --git a/backend/uploads/images/bff586c7-6343-45e0-8e8b-a0cce3390544.webp b/backend/uploads/images/bff586c7-6343-45e0-8e8b-a0cce3390544.webp new file mode 100644 index 0000000..0a148c5 Binary files /dev/null and b/backend/uploads/images/bff586c7-6343-45e0-8e8b-a0cce3390544.webp differ diff --git a/backend/uploads/images/c0f03963-355f-41e9-9e31-cf5cc54da1cd.webp b/backend/uploads/images/c0f03963-355f-41e9-9e31-cf5cc54da1cd.webp new file mode 100644 index 0000000..37da5df Binary files /dev/null and b/backend/uploads/images/c0f03963-355f-41e9-9e31-cf5cc54da1cd.webp differ diff --git a/backend/uploads/images/c10b85d6-4297-48a4-b805-90045d17cd9d.webp b/backend/uploads/images/c10b85d6-4297-48a4-b805-90045d17cd9d.webp new file mode 100644 index 0000000..f2d3426 Binary files /dev/null and b/backend/uploads/images/c10b85d6-4297-48a4-b805-90045d17cd9d.webp differ diff --git a/backend/uploads/images/c125dbba-da74-4c0c-87a8-56c5a0e4efbf.webp b/backend/uploads/images/c125dbba-da74-4c0c-87a8-56c5a0e4efbf.webp new file mode 100644 index 0000000..83f4c9f Binary files /dev/null and b/backend/uploads/images/c125dbba-da74-4c0c-87a8-56c5a0e4efbf.webp differ diff --git a/backend/uploads/images/c173e40d-4b49-4ee8-a2a7-940a8288d1d7.webp b/backend/uploads/images/c173e40d-4b49-4ee8-a2a7-940a8288d1d7.webp new file mode 100644 index 0000000..76be12f Binary files /dev/null and b/backend/uploads/images/c173e40d-4b49-4ee8-a2a7-940a8288d1d7.webp differ diff --git a/backend/uploads/images/c1e57661-2df6-4ef0-ab62-cd09e2e7023d.webp b/backend/uploads/images/c1e57661-2df6-4ef0-ab62-cd09e2e7023d.webp new file mode 100644 index 0000000..81a239c Binary files /dev/null and b/backend/uploads/images/c1e57661-2df6-4ef0-ab62-cd09e2e7023d.webp differ diff --git a/backend/uploads/images/c21fee9c-021c-4955-8527-a9fa1bab9a1b.webp b/backend/uploads/images/c21fee9c-021c-4955-8527-a9fa1bab9a1b.webp new file mode 100644 index 0000000..464a623 Binary files /dev/null and b/backend/uploads/images/c21fee9c-021c-4955-8527-a9fa1bab9a1b.webp differ diff --git a/backend/uploads/images/c23042e7-bb9f-4463-ba4b-8a82498fc883.webp b/backend/uploads/images/c23042e7-bb9f-4463-ba4b-8a82498fc883.webp new file mode 100644 index 0000000..243f421 Binary files /dev/null and b/backend/uploads/images/c23042e7-bb9f-4463-ba4b-8a82498fc883.webp differ diff --git a/backend/uploads/images/c2bd2203-a36b-4c3f-a4af-bdeaa1d25228.webp b/backend/uploads/images/c2bd2203-a36b-4c3f-a4af-bdeaa1d25228.webp new file mode 100644 index 0000000..2782b55 Binary files /dev/null and b/backend/uploads/images/c2bd2203-a36b-4c3f-a4af-bdeaa1d25228.webp differ diff --git a/backend/uploads/images/c2c72565-bb2d-4e30-907d-616bf8cf2dde.webp b/backend/uploads/images/c2c72565-bb2d-4e30-907d-616bf8cf2dde.webp new file mode 100644 index 0000000..9b5ac27 Binary files /dev/null and b/backend/uploads/images/c2c72565-bb2d-4e30-907d-616bf8cf2dde.webp differ diff --git a/backend/uploads/images/c30d647f-8b74-4358-b825-527aa07e8aa3.webp b/backend/uploads/images/c30d647f-8b74-4358-b825-527aa07e8aa3.webp new file mode 100644 index 0000000..28aef36 Binary files /dev/null and b/backend/uploads/images/c30d647f-8b74-4358-b825-527aa07e8aa3.webp differ diff --git a/backend/uploads/images/c3db3158-1116-449e-8d3e-97be63c4a985.webp b/backend/uploads/images/c3db3158-1116-449e-8d3e-97be63c4a985.webp new file mode 100644 index 0000000..84c3b74 Binary files /dev/null and b/backend/uploads/images/c3db3158-1116-449e-8d3e-97be63c4a985.webp differ diff --git a/backend/uploads/images/c3e9d82b-69c1-434b-b19b-b2ec4259c487.webp b/backend/uploads/images/c3e9d82b-69c1-434b-b19b-b2ec4259c487.webp new file mode 100644 index 0000000..5317ad2 Binary files /dev/null and b/backend/uploads/images/c3e9d82b-69c1-434b-b19b-b2ec4259c487.webp differ diff --git a/backend/uploads/images/c41c2e6e-27e3-46af-9f79-6240201d6b2e.webp b/backend/uploads/images/c41c2e6e-27e3-46af-9f79-6240201d6b2e.webp new file mode 100644 index 0000000..7f9528f Binary files /dev/null and b/backend/uploads/images/c41c2e6e-27e3-46af-9f79-6240201d6b2e.webp differ diff --git a/backend/uploads/images/c4580145-560a-4f35-a211-73d348f952e4.webp b/backend/uploads/images/c4580145-560a-4f35-a211-73d348f952e4.webp new file mode 100644 index 0000000..98f91d7 Binary files /dev/null and b/backend/uploads/images/c4580145-560a-4f35-a211-73d348f952e4.webp differ diff --git a/backend/uploads/images/c608e3a3-6486-423e-8222-e41418f6971e.webp b/backend/uploads/images/c608e3a3-6486-423e-8222-e41418f6971e.webp new file mode 100644 index 0000000..b2007ed Binary files /dev/null and b/backend/uploads/images/c608e3a3-6486-423e-8222-e41418f6971e.webp differ diff --git a/backend/uploads/images/c6e1c4c9-815e-474d-9bc4-100924d362ea.webp b/backend/uploads/images/c6e1c4c9-815e-474d-9bc4-100924d362ea.webp new file mode 100644 index 0000000..eadcac2 Binary files /dev/null and b/backend/uploads/images/c6e1c4c9-815e-474d-9bc4-100924d362ea.webp differ diff --git a/backend/uploads/images/c7b71405-17b9-4d13-9789-ed4ebbb5cd23.webp b/backend/uploads/images/c7b71405-17b9-4d13-9789-ed4ebbb5cd23.webp new file mode 100644 index 0000000..d402756 Binary files /dev/null and b/backend/uploads/images/c7b71405-17b9-4d13-9789-ed4ebbb5cd23.webp differ diff --git a/backend/uploads/images/c7e60421-d02c-4123-9e1b-dce7867251a9.webp b/backend/uploads/images/c7e60421-d02c-4123-9e1b-dce7867251a9.webp new file mode 100644 index 0000000..6e012aa Binary files /dev/null and b/backend/uploads/images/c7e60421-d02c-4123-9e1b-dce7867251a9.webp differ diff --git a/backend/uploads/images/c86a875c-6a96-4b25-ab64-10e26da792e9.webp b/backend/uploads/images/c86a875c-6a96-4b25-ab64-10e26da792e9.webp new file mode 100644 index 0000000..82fd50c Binary files /dev/null and b/backend/uploads/images/c86a875c-6a96-4b25-ab64-10e26da792e9.webp differ diff --git a/backend/uploads/images/c8f2d2ab-b1b8-4d6e-96e3-15cfec3d0ffe.webp b/backend/uploads/images/c8f2d2ab-b1b8-4d6e-96e3-15cfec3d0ffe.webp new file mode 100644 index 0000000..f6ce9da Binary files /dev/null and b/backend/uploads/images/c8f2d2ab-b1b8-4d6e-96e3-15cfec3d0ffe.webp differ diff --git a/backend/uploads/images/c9826e35-8e8e-473b-8654-52f1b002f30e.webp b/backend/uploads/images/c9826e35-8e8e-473b-8654-52f1b002f30e.webp new file mode 100644 index 0000000..a7ef492 Binary files /dev/null and b/backend/uploads/images/c9826e35-8e8e-473b-8654-52f1b002f30e.webp differ diff --git a/backend/uploads/images/c9cd7e83-38f7-4d20-af66-3a45052950bf.webp b/backend/uploads/images/c9cd7e83-38f7-4d20-af66-3a45052950bf.webp new file mode 100644 index 0000000..a958995 Binary files /dev/null and b/backend/uploads/images/c9cd7e83-38f7-4d20-af66-3a45052950bf.webp differ diff --git a/backend/uploads/images/ca3e7929-1165-4ac3-b7be-8081973940f8.webp b/backend/uploads/images/ca3e7929-1165-4ac3-b7be-8081973940f8.webp new file mode 100644 index 0000000..254c805 Binary files /dev/null and b/backend/uploads/images/ca3e7929-1165-4ac3-b7be-8081973940f8.webp differ diff --git a/backend/uploads/images/ca5580b1-dc87-47b1-a1ab-bc792f65bc06.webp b/backend/uploads/images/ca5580b1-dc87-47b1-a1ab-bc792f65bc06.webp new file mode 100644 index 0000000..6e4cef5 Binary files /dev/null and b/backend/uploads/images/ca5580b1-dc87-47b1-a1ab-bc792f65bc06.webp differ diff --git a/backend/uploads/images/ca986118-ef00-4e6f-9626-f9612d0ca34d.webp b/backend/uploads/images/ca986118-ef00-4e6f-9626-f9612d0ca34d.webp new file mode 100644 index 0000000..fc93f87 Binary files /dev/null and b/backend/uploads/images/ca986118-ef00-4e6f-9626-f9612d0ca34d.webp differ diff --git a/backend/uploads/images/cb1ab96b-2824-49fd-8472-35c9283f04fc.webp b/backend/uploads/images/cb1ab96b-2824-49fd-8472-35c9283f04fc.webp new file mode 100644 index 0000000..0eda36f Binary files /dev/null and b/backend/uploads/images/cb1ab96b-2824-49fd-8472-35c9283f04fc.webp differ diff --git a/backend/uploads/images/cb4d5661-84f2-47c7-a788-1039b0a8ea5d.webp b/backend/uploads/images/cb4d5661-84f2-47c7-a788-1039b0a8ea5d.webp new file mode 100644 index 0000000..8a451a1 Binary files /dev/null and b/backend/uploads/images/cb4d5661-84f2-47c7-a788-1039b0a8ea5d.webp differ diff --git a/backend/uploads/images/cb8d78f2-83e6-49f9-a38d-b2545bd33f77.webp b/backend/uploads/images/cb8d78f2-83e6-49f9-a38d-b2545bd33f77.webp new file mode 100644 index 0000000..8242ce1 Binary files /dev/null and b/backend/uploads/images/cb8d78f2-83e6-49f9-a38d-b2545bd33f77.webp differ diff --git a/backend/uploads/images/cb96a4be-68f8-410b-8c35-f4a17f3d8d97.webp b/backend/uploads/images/cb96a4be-68f8-410b-8c35-f4a17f3d8d97.webp new file mode 100644 index 0000000..d33c860 Binary files /dev/null and b/backend/uploads/images/cb96a4be-68f8-410b-8c35-f4a17f3d8d97.webp differ diff --git a/backend/uploads/images/cccee822-8725-4ff0-9975-174155be425e.webp b/backend/uploads/images/cccee822-8725-4ff0-9975-174155be425e.webp new file mode 100644 index 0000000..ca16c19 Binary files /dev/null and b/backend/uploads/images/cccee822-8725-4ff0-9975-174155be425e.webp differ diff --git a/backend/uploads/images/cd7d76c1-fcad-41ac-b236-c9241adcc3aa.webp b/backend/uploads/images/cd7d76c1-fcad-41ac-b236-c9241adcc3aa.webp new file mode 100644 index 0000000..197f75a Binary files /dev/null and b/backend/uploads/images/cd7d76c1-fcad-41ac-b236-c9241adcc3aa.webp differ diff --git a/backend/uploads/images/cdbbadc3-5e9f-4ede-a1cd-6350d784579b.webp b/backend/uploads/images/cdbbadc3-5e9f-4ede-a1cd-6350d784579b.webp new file mode 100644 index 0000000..d6b98e0 Binary files /dev/null and b/backend/uploads/images/cdbbadc3-5e9f-4ede-a1cd-6350d784579b.webp differ diff --git a/backend/uploads/images/cdd7d8ce-9b1f-4e29-8f3b-f11762ba7434.webp b/backend/uploads/images/cdd7d8ce-9b1f-4e29-8f3b-f11762ba7434.webp new file mode 100644 index 0000000..e4557e3 Binary files /dev/null and b/backend/uploads/images/cdd7d8ce-9b1f-4e29-8f3b-f11762ba7434.webp differ diff --git a/backend/uploads/images/ce008344-1a22-4d23-9010-9625669a2460.webp b/backend/uploads/images/ce008344-1a22-4d23-9010-9625669a2460.webp new file mode 100644 index 0000000..d957dde Binary files /dev/null and b/backend/uploads/images/ce008344-1a22-4d23-9010-9625669a2460.webp differ diff --git a/backend/uploads/images/ce1846d1-7739-454e-83b9-98021bcea496.webp b/backend/uploads/images/ce1846d1-7739-454e-83b9-98021bcea496.webp new file mode 100644 index 0000000..feba910 Binary files /dev/null and b/backend/uploads/images/ce1846d1-7739-454e-83b9-98021bcea496.webp differ diff --git a/backend/uploads/images/ce268ea9-6a07-4996-a7a4-48ed57124028.webp b/backend/uploads/images/ce268ea9-6a07-4996-a7a4-48ed57124028.webp new file mode 100644 index 0000000..03db92a Binary files /dev/null and b/backend/uploads/images/ce268ea9-6a07-4996-a7a4-48ed57124028.webp differ diff --git a/backend/uploads/images/ceed814c-5a7f-4067-9426-59fc1ad0a97a.webp b/backend/uploads/images/ceed814c-5a7f-4067-9426-59fc1ad0a97a.webp new file mode 100644 index 0000000..a99c638 Binary files /dev/null and b/backend/uploads/images/ceed814c-5a7f-4067-9426-59fc1ad0a97a.webp differ diff --git a/backend/uploads/images/ceed8938-f6a4-4e2d-89ea-a833165c89cd.webp b/backend/uploads/images/ceed8938-f6a4-4e2d-89ea-a833165c89cd.webp new file mode 100644 index 0000000..cecc18d Binary files /dev/null and b/backend/uploads/images/ceed8938-f6a4-4e2d-89ea-a833165c89cd.webp differ diff --git a/backend/uploads/images/cf0e3973-64ed-4056-a6fa-3bf6604ab742.webp b/backend/uploads/images/cf0e3973-64ed-4056-a6fa-3bf6604ab742.webp new file mode 100644 index 0000000..a3c1bbb Binary files /dev/null and b/backend/uploads/images/cf0e3973-64ed-4056-a6fa-3bf6604ab742.webp differ diff --git a/backend/uploads/images/cf37de2a-8605-49ca-b937-000519f89197.webp b/backend/uploads/images/cf37de2a-8605-49ca-b937-000519f89197.webp new file mode 100644 index 0000000..45bac01 Binary files /dev/null and b/backend/uploads/images/cf37de2a-8605-49ca-b937-000519f89197.webp differ diff --git a/backend/uploads/images/cfa0d7d0-d2da-4959-a9ef-3bffd2dfa625.webp b/backend/uploads/images/cfa0d7d0-d2da-4959-a9ef-3bffd2dfa625.webp new file mode 100644 index 0000000..9e9ace6 Binary files /dev/null and b/backend/uploads/images/cfa0d7d0-d2da-4959-a9ef-3bffd2dfa625.webp differ diff --git a/backend/uploads/images/cfde0aff-1a77-49b7-968f-da7c0da1917b.webp b/backend/uploads/images/cfde0aff-1a77-49b7-968f-da7c0da1917b.webp new file mode 100644 index 0000000..3f71360 Binary files /dev/null and b/backend/uploads/images/cfde0aff-1a77-49b7-968f-da7c0da1917b.webp differ diff --git a/backend/uploads/images/d01f8ae6-30f3-443c-82f5-79f716822405.webp b/backend/uploads/images/d01f8ae6-30f3-443c-82f5-79f716822405.webp new file mode 100644 index 0000000..003924f Binary files /dev/null and b/backend/uploads/images/d01f8ae6-30f3-443c-82f5-79f716822405.webp differ diff --git a/backend/uploads/images/d037dbe7-cd83-417d-a6bd-e7f3d95a0f8a.webp b/backend/uploads/images/d037dbe7-cd83-417d-a6bd-e7f3d95a0f8a.webp new file mode 100644 index 0000000..07076ff Binary files /dev/null and b/backend/uploads/images/d037dbe7-cd83-417d-a6bd-e7f3d95a0f8a.webp differ diff --git a/backend/uploads/images/d03a3c3f-3c5d-4aa2-869d-3222b890201e.webp b/backend/uploads/images/d03a3c3f-3c5d-4aa2-869d-3222b890201e.webp new file mode 100644 index 0000000..a4c2c96 Binary files /dev/null and b/backend/uploads/images/d03a3c3f-3c5d-4aa2-869d-3222b890201e.webp differ diff --git a/backend/uploads/images/d054e49b-ee2c-4204-ab32-cdf0b7c42b8f.webp b/backend/uploads/images/d054e49b-ee2c-4204-ab32-cdf0b7c42b8f.webp new file mode 100644 index 0000000..83241f0 Binary files /dev/null and b/backend/uploads/images/d054e49b-ee2c-4204-ab32-cdf0b7c42b8f.webp differ diff --git a/backend/uploads/images/d088e78c-e9d9-461c-97c4-44b29518489b.webp b/backend/uploads/images/d088e78c-e9d9-461c-97c4-44b29518489b.webp new file mode 100644 index 0000000..8bcfea7 Binary files /dev/null and b/backend/uploads/images/d088e78c-e9d9-461c-97c4-44b29518489b.webp differ diff --git a/backend/uploads/images/d15c5dc1-1674-46bd-8659-b08401fa00f6.webp b/backend/uploads/images/d15c5dc1-1674-46bd-8659-b08401fa00f6.webp new file mode 100644 index 0000000..6042d0f Binary files /dev/null and b/backend/uploads/images/d15c5dc1-1674-46bd-8659-b08401fa00f6.webp differ diff --git a/backend/uploads/images/d208d1d8-e5ef-4b0c-ba64-e34d20550c3d.webp b/backend/uploads/images/d208d1d8-e5ef-4b0c-ba64-e34d20550c3d.webp new file mode 100644 index 0000000..74f7fd0 Binary files /dev/null and b/backend/uploads/images/d208d1d8-e5ef-4b0c-ba64-e34d20550c3d.webp differ diff --git a/backend/uploads/images/d2d15bf0-f350-4a83-9e94-97a22472c56a.webp b/backend/uploads/images/d2d15bf0-f350-4a83-9e94-97a22472c56a.webp new file mode 100644 index 0000000..5240384 Binary files /dev/null and b/backend/uploads/images/d2d15bf0-f350-4a83-9e94-97a22472c56a.webp differ diff --git a/backend/uploads/images/d3239e0e-577d-4b2b-b0c0-9e933a31473b.webp b/backend/uploads/images/d3239e0e-577d-4b2b-b0c0-9e933a31473b.webp new file mode 100644 index 0000000..f391bd1 Binary files /dev/null and b/backend/uploads/images/d3239e0e-577d-4b2b-b0c0-9e933a31473b.webp differ diff --git a/backend/uploads/images/d376d17f-544f-47fa-b45e-49d5300275d7.webp b/backend/uploads/images/d376d17f-544f-47fa-b45e-49d5300275d7.webp new file mode 100644 index 0000000..cb307d0 Binary files /dev/null and b/backend/uploads/images/d376d17f-544f-47fa-b45e-49d5300275d7.webp differ diff --git a/backend/uploads/images/d3e2568a-a276-4d7d-96be-947fc0dfe404.webp b/backend/uploads/images/d3e2568a-a276-4d7d-96be-947fc0dfe404.webp new file mode 100644 index 0000000..04f849f Binary files /dev/null and b/backend/uploads/images/d3e2568a-a276-4d7d-96be-947fc0dfe404.webp differ diff --git a/backend/uploads/images/d3f3839d-9933-4c6c-beee-47f8d8280219.webp b/backend/uploads/images/d3f3839d-9933-4c6c-beee-47f8d8280219.webp new file mode 100644 index 0000000..e9f1a0b Binary files /dev/null and b/backend/uploads/images/d3f3839d-9933-4c6c-beee-47f8d8280219.webp differ diff --git a/backend/uploads/images/d4dee218-4b2c-4e73-9a2a-ac60b8c27418.webp b/backend/uploads/images/d4dee218-4b2c-4e73-9a2a-ac60b8c27418.webp new file mode 100644 index 0000000..4d74417 Binary files /dev/null and b/backend/uploads/images/d4dee218-4b2c-4e73-9a2a-ac60b8c27418.webp differ diff --git a/backend/uploads/images/d5d46a5e-9571-4b9d-bfe3-5112a141a043.webp b/backend/uploads/images/d5d46a5e-9571-4b9d-bfe3-5112a141a043.webp new file mode 100644 index 0000000..d3faca4 Binary files /dev/null and b/backend/uploads/images/d5d46a5e-9571-4b9d-bfe3-5112a141a043.webp differ diff --git a/backend/uploads/images/d6ec882b-50c8-462b-b179-e4b7a96b69ce.webp b/backend/uploads/images/d6ec882b-50c8-462b-b179-e4b7a96b69ce.webp new file mode 100644 index 0000000..504edef Binary files /dev/null and b/backend/uploads/images/d6ec882b-50c8-462b-b179-e4b7a96b69ce.webp differ diff --git a/backend/uploads/images/d711ceea-4396-4f92-9e47-c869124feeeb.webp b/backend/uploads/images/d711ceea-4396-4f92-9e47-c869124feeeb.webp new file mode 100644 index 0000000..a5dae33 Binary files /dev/null and b/backend/uploads/images/d711ceea-4396-4f92-9e47-c869124feeeb.webp differ diff --git a/backend/uploads/images/d7421ea1-da80-461f-98d2-03e3123b0f93.webp b/backend/uploads/images/d7421ea1-da80-461f-98d2-03e3123b0f93.webp new file mode 100644 index 0000000..f2fd39c Binary files /dev/null and b/backend/uploads/images/d7421ea1-da80-461f-98d2-03e3123b0f93.webp differ diff --git a/backend/uploads/images/d76c024b-d875-4458-80bf-20d1d23744fe.webp b/backend/uploads/images/d76c024b-d875-4458-80bf-20d1d23744fe.webp new file mode 100644 index 0000000..66222f5 Binary files /dev/null and b/backend/uploads/images/d76c024b-d875-4458-80bf-20d1d23744fe.webp differ diff --git a/backend/uploads/images/d79cb505-c2ce-42a5-9e73-0ba2faf1d308.webp b/backend/uploads/images/d79cb505-c2ce-42a5-9e73-0ba2faf1d308.webp new file mode 100644 index 0000000..551502f Binary files /dev/null and b/backend/uploads/images/d79cb505-c2ce-42a5-9e73-0ba2faf1d308.webp differ diff --git a/backend/uploads/images/d829a4f0-cdb0-4f9b-ab24-8d0368c48280.webp b/backend/uploads/images/d829a4f0-cdb0-4f9b-ab24-8d0368c48280.webp new file mode 100644 index 0000000..9aeb362 Binary files /dev/null and b/backend/uploads/images/d829a4f0-cdb0-4f9b-ab24-8d0368c48280.webp differ diff --git a/backend/uploads/images/d86410b0-1d6d-4c34-89ea-28eb872a842d.webp b/backend/uploads/images/d86410b0-1d6d-4c34-89ea-28eb872a842d.webp new file mode 100644 index 0000000..65d7bed Binary files /dev/null and b/backend/uploads/images/d86410b0-1d6d-4c34-89ea-28eb872a842d.webp differ diff --git a/backend/uploads/images/d8a8fca4-5082-4151-a2d0-63a1f039a3f6.webp b/backend/uploads/images/d8a8fca4-5082-4151-a2d0-63a1f039a3f6.webp new file mode 100644 index 0000000..4e37d07 Binary files /dev/null and b/backend/uploads/images/d8a8fca4-5082-4151-a2d0-63a1f039a3f6.webp differ diff --git a/backend/uploads/images/d8c86160-a426-4dc7-be3d-2fd66d80591a.webp b/backend/uploads/images/d8c86160-a426-4dc7-be3d-2fd66d80591a.webp new file mode 100644 index 0000000..91ea2de Binary files /dev/null and b/backend/uploads/images/d8c86160-a426-4dc7-be3d-2fd66d80591a.webp differ diff --git a/backend/uploads/images/d8def488-abd3-48b0-949d-a3e7e97575e7.webp b/backend/uploads/images/d8def488-abd3-48b0-949d-a3e7e97575e7.webp new file mode 100644 index 0000000..d631989 Binary files /dev/null and b/backend/uploads/images/d8def488-abd3-48b0-949d-a3e7e97575e7.webp differ diff --git a/backend/uploads/images/d926197b-2cbf-4ca3-9383-3c411b3a99b4.webp b/backend/uploads/images/d926197b-2cbf-4ca3-9383-3c411b3a99b4.webp new file mode 100644 index 0000000..0fbc771 Binary files /dev/null and b/backend/uploads/images/d926197b-2cbf-4ca3-9383-3c411b3a99b4.webp differ diff --git a/backend/uploads/images/d9271740-4efc-4081-bb57-d6b44af824fc.webp b/backend/uploads/images/d9271740-4efc-4081-bb57-d6b44af824fc.webp new file mode 100644 index 0000000..7a69278 Binary files /dev/null and b/backend/uploads/images/d9271740-4efc-4081-bb57-d6b44af824fc.webp differ diff --git a/backend/uploads/images/d9fca590-201e-4627-9753-315af60c4f52.webp b/backend/uploads/images/d9fca590-201e-4627-9753-315af60c4f52.webp new file mode 100644 index 0000000..54f7cb8 Binary files /dev/null and b/backend/uploads/images/d9fca590-201e-4627-9753-315af60c4f52.webp differ diff --git a/backend/uploads/images/da2dff64-adcb-4789-8e58-bf186d48dd1f.webp b/backend/uploads/images/da2dff64-adcb-4789-8e58-bf186d48dd1f.webp new file mode 100644 index 0000000..48d855a Binary files /dev/null and b/backend/uploads/images/da2dff64-adcb-4789-8e58-bf186d48dd1f.webp differ diff --git a/backend/uploads/images/da598cfa-3b44-4a52-9ec6-4eb4d426df5d.webp b/backend/uploads/images/da598cfa-3b44-4a52-9ec6-4eb4d426df5d.webp new file mode 100644 index 0000000..0437fd9 Binary files /dev/null and b/backend/uploads/images/da598cfa-3b44-4a52-9ec6-4eb4d426df5d.webp differ diff --git a/backend/uploads/images/da635a52-2e24-4227-8f16-a05b3cc3e5e6.webp b/backend/uploads/images/da635a52-2e24-4227-8f16-a05b3cc3e5e6.webp new file mode 100644 index 0000000..85e5e83 Binary files /dev/null and b/backend/uploads/images/da635a52-2e24-4227-8f16-a05b3cc3e5e6.webp differ diff --git a/backend/uploads/images/da7d8687-1982-4ed5-8eed-51d0c885ac4e.webp b/backend/uploads/images/da7d8687-1982-4ed5-8eed-51d0c885ac4e.webp new file mode 100644 index 0000000..25a39ce Binary files /dev/null and b/backend/uploads/images/da7d8687-1982-4ed5-8eed-51d0c885ac4e.webp differ diff --git a/backend/uploads/images/da937514-7d34-4900-ab39-10b62aa60aba.webp b/backend/uploads/images/da937514-7d34-4900-ab39-10b62aa60aba.webp new file mode 100644 index 0000000..41816b3 Binary files /dev/null and b/backend/uploads/images/da937514-7d34-4900-ab39-10b62aa60aba.webp differ diff --git a/backend/uploads/images/daa700a2-4a19-4249-a314-485fbafa0492.webp b/backend/uploads/images/daa700a2-4a19-4249-a314-485fbafa0492.webp new file mode 100644 index 0000000..3ec78ad Binary files /dev/null and b/backend/uploads/images/daa700a2-4a19-4249-a314-485fbafa0492.webp differ diff --git a/backend/uploads/images/db2de14e-d1d9-42df-9b21-4e2778e114e0.webp b/backend/uploads/images/db2de14e-d1d9-42df-9b21-4e2778e114e0.webp new file mode 100644 index 0000000..61f8697 Binary files /dev/null and b/backend/uploads/images/db2de14e-d1d9-42df-9b21-4e2778e114e0.webp differ diff --git a/backend/uploads/images/dbb2face-aaca-43e7-8c11-4b8be95d816e.webp b/backend/uploads/images/dbb2face-aaca-43e7-8c11-4b8be95d816e.webp new file mode 100644 index 0000000..03c744b Binary files /dev/null and b/backend/uploads/images/dbb2face-aaca-43e7-8c11-4b8be95d816e.webp differ diff --git a/backend/uploads/images/dc955f24-3f1e-44c0-8050-d26363982d8e.webp b/backend/uploads/images/dc955f24-3f1e-44c0-8050-d26363982d8e.webp new file mode 100644 index 0000000..0c21a39 Binary files /dev/null and b/backend/uploads/images/dc955f24-3f1e-44c0-8050-d26363982d8e.webp differ diff --git a/backend/uploads/images/dca6f584-2b50-492d-bae3-70cbcdf24339.webp b/backend/uploads/images/dca6f584-2b50-492d-bae3-70cbcdf24339.webp new file mode 100644 index 0000000..d4c2fcf Binary files /dev/null and b/backend/uploads/images/dca6f584-2b50-492d-bae3-70cbcdf24339.webp differ diff --git a/backend/uploads/images/dcf1319d-7c82-4b21-af1e-429c6812fbc6.webp b/backend/uploads/images/dcf1319d-7c82-4b21-af1e-429c6812fbc6.webp new file mode 100644 index 0000000..3fececa Binary files /dev/null and b/backend/uploads/images/dcf1319d-7c82-4b21-af1e-429c6812fbc6.webp differ diff --git a/backend/uploads/images/dd47bf88-5807-4c72-a9b2-3367b5cf798f.webp b/backend/uploads/images/dd47bf88-5807-4c72-a9b2-3367b5cf798f.webp new file mode 100644 index 0000000..1679745 Binary files /dev/null and b/backend/uploads/images/dd47bf88-5807-4c72-a9b2-3367b5cf798f.webp differ diff --git a/backend/uploads/images/dd746600-f783-4390-8e54-b5e1e862a4a4.webp b/backend/uploads/images/dd746600-f783-4390-8e54-b5e1e862a4a4.webp new file mode 100644 index 0000000..1e1cfd6 Binary files /dev/null and b/backend/uploads/images/dd746600-f783-4390-8e54-b5e1e862a4a4.webp differ diff --git a/backend/uploads/images/dda8ad7f-df7e-43f7-be0c-d2f4f27ff991.webp b/backend/uploads/images/dda8ad7f-df7e-43f7-be0c-d2f4f27ff991.webp new file mode 100644 index 0000000..8df98c2 Binary files /dev/null and b/backend/uploads/images/dda8ad7f-df7e-43f7-be0c-d2f4f27ff991.webp differ diff --git a/backend/uploads/images/debfb809-84d5-4bfd-bbb6-40dc53c95f17.webp b/backend/uploads/images/debfb809-84d5-4bfd-bbb6-40dc53c95f17.webp new file mode 100644 index 0000000..41816b3 Binary files /dev/null and b/backend/uploads/images/debfb809-84d5-4bfd-bbb6-40dc53c95f17.webp differ diff --git a/backend/uploads/images/df2883cd-0cdd-40e4-bbfc-79bb3f002402.webp b/backend/uploads/images/df2883cd-0cdd-40e4-bbfc-79bb3f002402.webp new file mode 100644 index 0000000..6396805 Binary files /dev/null and b/backend/uploads/images/df2883cd-0cdd-40e4-bbfc-79bb3f002402.webp differ diff --git a/backend/uploads/images/df8228bf-4b68-49c7-a219-e29880009ddf.webp b/backend/uploads/images/df8228bf-4b68-49c7-a219-e29880009ddf.webp new file mode 100644 index 0000000..b668f9e Binary files /dev/null and b/backend/uploads/images/df8228bf-4b68-49c7-a219-e29880009ddf.webp differ diff --git a/backend/uploads/images/df8be750-6859-4c93-a3a3-cdf2a832b915.webp b/backend/uploads/images/df8be750-6859-4c93-a3a3-cdf2a832b915.webp new file mode 100644 index 0000000..6eaf69c Binary files /dev/null and b/backend/uploads/images/df8be750-6859-4c93-a3a3-cdf2a832b915.webp differ diff --git a/backend/uploads/images/dfd903ed-eaba-41d2-8a21-386fb9bbaf60.webp b/backend/uploads/images/dfd903ed-eaba-41d2-8a21-386fb9bbaf60.webp new file mode 100644 index 0000000..78e0e3d Binary files /dev/null and b/backend/uploads/images/dfd903ed-eaba-41d2-8a21-386fb9bbaf60.webp differ diff --git a/backend/uploads/images/dfe563b7-cbb7-459c-9b4a-2f432de73fd3.webp b/backend/uploads/images/dfe563b7-cbb7-459c-9b4a-2f432de73fd3.webp new file mode 100644 index 0000000..45090f9 Binary files /dev/null and b/backend/uploads/images/dfe563b7-cbb7-459c-9b4a-2f432de73fd3.webp differ diff --git a/backend/uploads/images/e05d9a8e-760b-4c4d-9134-d7b918644a3e.webp b/backend/uploads/images/e05d9a8e-760b-4c4d-9134-d7b918644a3e.webp new file mode 100644 index 0000000..1cca1d0 Binary files /dev/null and b/backend/uploads/images/e05d9a8e-760b-4c4d-9134-d7b918644a3e.webp differ diff --git a/backend/uploads/images/e05ef7b4-30f2-4de3-a7f4-6bb79576bd4b.webp b/backend/uploads/images/e05ef7b4-30f2-4de3-a7f4-6bb79576bd4b.webp new file mode 100644 index 0000000..14a4f6a Binary files /dev/null and b/backend/uploads/images/e05ef7b4-30f2-4de3-a7f4-6bb79576bd4b.webp differ diff --git a/backend/uploads/images/e075da53-4e22-4922-bbaa-5369200cf365.webp b/backend/uploads/images/e075da53-4e22-4922-bbaa-5369200cf365.webp new file mode 100644 index 0000000..403f4f7 Binary files /dev/null and b/backend/uploads/images/e075da53-4e22-4922-bbaa-5369200cf365.webp differ diff --git a/backend/uploads/images/e10adb32-e867-47c1-8514-9b1cb727e5f9.webp b/backend/uploads/images/e10adb32-e867-47c1-8514-9b1cb727e5f9.webp new file mode 100644 index 0000000..b37e86e Binary files /dev/null and b/backend/uploads/images/e10adb32-e867-47c1-8514-9b1cb727e5f9.webp differ diff --git a/backend/uploads/images/e13f05fc-bf9b-451e-a50f-8bc40a431b26.webp b/backend/uploads/images/e13f05fc-bf9b-451e-a50f-8bc40a431b26.webp new file mode 100644 index 0000000..9fb1070 Binary files /dev/null and b/backend/uploads/images/e13f05fc-bf9b-451e-a50f-8bc40a431b26.webp differ diff --git a/backend/uploads/images/e2c249d8-5c15-4b27-86f2-77711f124e85.webp b/backend/uploads/images/e2c249d8-5c15-4b27-86f2-77711f124e85.webp new file mode 100644 index 0000000..d9c1351 Binary files /dev/null and b/backend/uploads/images/e2c249d8-5c15-4b27-86f2-77711f124e85.webp differ diff --git a/backend/uploads/images/e2ede7f4-7ef3-43ef-9eca-32993a616cc7.webp b/backend/uploads/images/e2ede7f4-7ef3-43ef-9eca-32993a616cc7.webp new file mode 100644 index 0000000..8215b8a Binary files /dev/null and b/backend/uploads/images/e2ede7f4-7ef3-43ef-9eca-32993a616cc7.webp differ diff --git a/backend/uploads/images/e3717bfd-6241-41a2-9f0e-bcf1752e6ebc.webp b/backend/uploads/images/e3717bfd-6241-41a2-9f0e-bcf1752e6ebc.webp new file mode 100644 index 0000000..d6dc2e8 Binary files /dev/null and b/backend/uploads/images/e3717bfd-6241-41a2-9f0e-bcf1752e6ebc.webp differ diff --git a/backend/uploads/images/e38224fd-e3b7-4f13-8b1a-b9a8847ea46a.webp b/backend/uploads/images/e38224fd-e3b7-4f13-8b1a-b9a8847ea46a.webp new file mode 100644 index 0000000..21909c6 Binary files /dev/null and b/backend/uploads/images/e38224fd-e3b7-4f13-8b1a-b9a8847ea46a.webp differ diff --git a/backend/uploads/images/e3ae699e-6578-4c30-b88c-f260cc83defb.webp b/backend/uploads/images/e3ae699e-6578-4c30-b88c-f260cc83defb.webp new file mode 100644 index 0000000..0740996 Binary files /dev/null and b/backend/uploads/images/e3ae699e-6578-4c30-b88c-f260cc83defb.webp differ diff --git a/backend/uploads/images/e46399f1-edb1-428c-98e7-b2403bb5d36a.webp b/backend/uploads/images/e46399f1-edb1-428c-98e7-b2403bb5d36a.webp new file mode 100644 index 0000000..726498c Binary files /dev/null and b/backend/uploads/images/e46399f1-edb1-428c-98e7-b2403bb5d36a.webp differ diff --git a/backend/uploads/images/e46a359c-e151-4d1c-97ea-bc2b4a2bd2bd.webp b/backend/uploads/images/e46a359c-e151-4d1c-97ea-bc2b4a2bd2bd.webp new file mode 100644 index 0000000..54ccc12 Binary files /dev/null and b/backend/uploads/images/e46a359c-e151-4d1c-97ea-bc2b4a2bd2bd.webp differ diff --git a/backend/uploads/images/e4b947c2-d503-4192-8d37-c4f4d970fb3d.webp b/backend/uploads/images/e4b947c2-d503-4192-8d37-c4f4d970fb3d.webp new file mode 100644 index 0000000..9ac247d Binary files /dev/null and b/backend/uploads/images/e4b947c2-d503-4192-8d37-c4f4d970fb3d.webp differ diff --git a/backend/uploads/images/e562461f-6a27-434e-a108-74c308a03537.webp b/backend/uploads/images/e562461f-6a27-434e-a108-74c308a03537.webp new file mode 100644 index 0000000..6de1224 Binary files /dev/null and b/backend/uploads/images/e562461f-6a27-434e-a108-74c308a03537.webp differ diff --git a/backend/uploads/images/e5d264ce-a7b8-47db-854e-37e75411c5bb.webp b/backend/uploads/images/e5d264ce-a7b8-47db-854e-37e75411c5bb.webp new file mode 100644 index 0000000..7544873 Binary files /dev/null and b/backend/uploads/images/e5d264ce-a7b8-47db-854e-37e75411c5bb.webp differ diff --git a/backend/uploads/images/e66635b7-6efe-499e-b88e-926aab4ba59f.webp b/backend/uploads/images/e66635b7-6efe-499e-b88e-926aab4ba59f.webp new file mode 100644 index 0000000..496b70d Binary files /dev/null and b/backend/uploads/images/e66635b7-6efe-499e-b88e-926aab4ba59f.webp differ diff --git a/backend/uploads/images/e67d79e3-da7b-4bbc-8542-f2feacccca2e.webp b/backend/uploads/images/e67d79e3-da7b-4bbc-8542-f2feacccca2e.webp new file mode 100644 index 0000000..ed91b11 Binary files /dev/null and b/backend/uploads/images/e67d79e3-da7b-4bbc-8542-f2feacccca2e.webp differ diff --git a/backend/uploads/images/e75a0bef-9578-49ed-949d-4348fd9b9d59.webp b/backend/uploads/images/e75a0bef-9578-49ed-949d-4348fd9b9d59.webp new file mode 100644 index 0000000..bc48de5 Binary files /dev/null and b/backend/uploads/images/e75a0bef-9578-49ed-949d-4348fd9b9d59.webp differ diff --git a/backend/uploads/images/e7612745-a7ce-4d0a-bb91-545c4e1ed313.webp b/backend/uploads/images/e7612745-a7ce-4d0a-bb91-545c4e1ed313.webp new file mode 100644 index 0000000..27d4446 Binary files /dev/null and b/backend/uploads/images/e7612745-a7ce-4d0a-bb91-545c4e1ed313.webp differ diff --git a/backend/uploads/images/e7b31e2c-f20d-4a28-a3f1-fbd510cbf8c9.webp b/backend/uploads/images/e7b31e2c-f20d-4a28-a3f1-fbd510cbf8c9.webp new file mode 100644 index 0000000..df48e21 Binary files /dev/null and b/backend/uploads/images/e7b31e2c-f20d-4a28-a3f1-fbd510cbf8c9.webp differ diff --git a/backend/uploads/images/e7e39f58-48c4-46e4-8ea9-30d688c6ab97.webp b/backend/uploads/images/e7e39f58-48c4-46e4-8ea9-30d688c6ab97.webp new file mode 100644 index 0000000..a480390 Binary files /dev/null and b/backend/uploads/images/e7e39f58-48c4-46e4-8ea9-30d688c6ab97.webp differ diff --git a/backend/uploads/images/e7eac513-effb-444a-931a-c0c29d38c4e7.webp b/backend/uploads/images/e7eac513-effb-444a-931a-c0c29d38c4e7.webp new file mode 100644 index 0000000..0e7dc44 Binary files /dev/null and b/backend/uploads/images/e7eac513-effb-444a-931a-c0c29d38c4e7.webp differ diff --git a/backend/uploads/images/e7fbeb8a-8bd9-45f7-8973-5df275cafd96.webp b/backend/uploads/images/e7fbeb8a-8bd9-45f7-8973-5df275cafd96.webp new file mode 100644 index 0000000..09e7b37 Binary files /dev/null and b/backend/uploads/images/e7fbeb8a-8bd9-45f7-8973-5df275cafd96.webp differ diff --git a/backend/uploads/images/e827d830-1b1b-4794-bca9-fc58d3265891.webp b/backend/uploads/images/e827d830-1b1b-4794-bca9-fc58d3265891.webp new file mode 100644 index 0000000..451934c Binary files /dev/null and b/backend/uploads/images/e827d830-1b1b-4794-bca9-fc58d3265891.webp differ diff --git a/backend/uploads/images/e896ef8e-58b1-489e-8b2e-81bdf0cd1510.webp b/backend/uploads/images/e896ef8e-58b1-489e-8b2e-81bdf0cd1510.webp new file mode 100644 index 0000000..3184fbf Binary files /dev/null and b/backend/uploads/images/e896ef8e-58b1-489e-8b2e-81bdf0cd1510.webp differ diff --git a/backend/uploads/images/e8b9c95d-83bf-41e0-a13d-bb12154b31f6.webp b/backend/uploads/images/e8b9c95d-83bf-41e0-a13d-bb12154b31f6.webp new file mode 100644 index 0000000..d9ca587 Binary files /dev/null and b/backend/uploads/images/e8b9c95d-83bf-41e0-a13d-bb12154b31f6.webp differ diff --git a/backend/uploads/images/e959aeec-a7e6-4196-b603-870866e077a4.webp b/backend/uploads/images/e959aeec-a7e6-4196-b603-870866e077a4.webp new file mode 100644 index 0000000..7b42ff4 Binary files /dev/null and b/backend/uploads/images/e959aeec-a7e6-4196-b603-870866e077a4.webp differ diff --git a/backend/uploads/images/e9ea3d31-22d1-4901-8eb6-db07a1e46e28.webp b/backend/uploads/images/e9ea3d31-22d1-4901-8eb6-db07a1e46e28.webp new file mode 100644 index 0000000..cda8dc8 Binary files /dev/null and b/backend/uploads/images/e9ea3d31-22d1-4901-8eb6-db07a1e46e28.webp differ diff --git a/backend/uploads/images/ea7e0de2-52ae-4c14-a2c4-1de29c69a14d.webp b/backend/uploads/images/ea7e0de2-52ae-4c14-a2c4-1de29c69a14d.webp new file mode 100644 index 0000000..5fe2567 Binary files /dev/null and b/backend/uploads/images/ea7e0de2-52ae-4c14-a2c4-1de29c69a14d.webp differ diff --git a/backend/uploads/images/ea8261b1-f30c-4929-81f8-518d11d0f309.webp b/backend/uploads/images/ea8261b1-f30c-4929-81f8-518d11d0f309.webp new file mode 100644 index 0000000..338a36f Binary files /dev/null and b/backend/uploads/images/ea8261b1-f30c-4929-81f8-518d11d0f309.webp differ diff --git a/backend/uploads/images/eb4482b7-7b81-4230-b85c-90860a08a562.webp b/backend/uploads/images/eb4482b7-7b81-4230-b85c-90860a08a562.webp new file mode 100644 index 0000000..ea085f5 Binary files /dev/null and b/backend/uploads/images/eb4482b7-7b81-4230-b85c-90860a08a562.webp differ diff --git a/backend/uploads/images/ebc7c811-bc05-404c-a737-9c87c4163642.webp b/backend/uploads/images/ebc7c811-bc05-404c-a737-9c87c4163642.webp new file mode 100644 index 0000000..f93e5f8 Binary files /dev/null and b/backend/uploads/images/ebc7c811-bc05-404c-a737-9c87c4163642.webp differ diff --git a/backend/uploads/images/ec06a92b-f627-47e7-9d55-65606ab15f36.webp b/backend/uploads/images/ec06a92b-f627-47e7-9d55-65606ab15f36.webp new file mode 100644 index 0000000..4c217c5 Binary files /dev/null and b/backend/uploads/images/ec06a92b-f627-47e7-9d55-65606ab15f36.webp differ diff --git a/backend/uploads/images/ec24004f-2ac2-49df-8093-b629647a2eaf.webp b/backend/uploads/images/ec24004f-2ac2-49df-8093-b629647a2eaf.webp new file mode 100644 index 0000000..177ea52 Binary files /dev/null and b/backend/uploads/images/ec24004f-2ac2-49df-8093-b629647a2eaf.webp differ diff --git a/backend/uploads/images/ec27e30e-959c-4a07-bae7-36ebd4b5f417.webp b/backend/uploads/images/ec27e30e-959c-4a07-bae7-36ebd4b5f417.webp new file mode 100644 index 0000000..abfa767 Binary files /dev/null and b/backend/uploads/images/ec27e30e-959c-4a07-bae7-36ebd4b5f417.webp differ diff --git a/backend/uploads/images/ec4e16f4-b0b4-4950-a19c-effd8c72346a.webp b/backend/uploads/images/ec4e16f4-b0b4-4950-a19c-effd8c72346a.webp new file mode 100644 index 0000000..ae15d82 Binary files /dev/null and b/backend/uploads/images/ec4e16f4-b0b4-4950-a19c-effd8c72346a.webp differ diff --git a/backend/uploads/images/ec617dc6-f171-4809-913e-cdd0f90b4757.webp b/backend/uploads/images/ec617dc6-f171-4809-913e-cdd0f90b4757.webp new file mode 100644 index 0000000..c856b61 Binary files /dev/null and b/backend/uploads/images/ec617dc6-f171-4809-913e-cdd0f90b4757.webp differ diff --git a/backend/uploads/images/ec6bc3e8-66ac-4759-868f-18133e7abc90.webp b/backend/uploads/images/ec6bc3e8-66ac-4759-868f-18133e7abc90.webp new file mode 100644 index 0000000..3589962 Binary files /dev/null and b/backend/uploads/images/ec6bc3e8-66ac-4759-868f-18133e7abc90.webp differ diff --git a/backend/uploads/images/ec81ddc1-97e2-4a8b-a005-2887f6b38527.webp b/backend/uploads/images/ec81ddc1-97e2-4a8b-a005-2887f6b38527.webp new file mode 100644 index 0000000..826c18f Binary files /dev/null and b/backend/uploads/images/ec81ddc1-97e2-4a8b-a005-2887f6b38527.webp differ diff --git a/backend/uploads/images/edf8f269-87ea-4c0f-b039-fd1ab057a8db.webp b/backend/uploads/images/edf8f269-87ea-4c0f-b039-fd1ab057a8db.webp new file mode 100644 index 0000000..58273bc Binary files /dev/null and b/backend/uploads/images/edf8f269-87ea-4c0f-b039-fd1ab057a8db.webp differ diff --git a/backend/uploads/images/ee33f6b5-6314-4dc9-8c71-dcade6eab1ce.webp b/backend/uploads/images/ee33f6b5-6314-4dc9-8c71-dcade6eab1ce.webp new file mode 100644 index 0000000..c133af9 Binary files /dev/null and b/backend/uploads/images/ee33f6b5-6314-4dc9-8c71-dcade6eab1ce.webp differ diff --git a/backend/uploads/images/ee741851-2247-44bc-9379-073da084691d.webp b/backend/uploads/images/ee741851-2247-44bc-9379-073da084691d.webp new file mode 100644 index 0000000..d212937 Binary files /dev/null and b/backend/uploads/images/ee741851-2247-44bc-9379-073da084691d.webp differ diff --git a/backend/uploads/images/ef259753-b9ee-45b7-86fc-d4381b5bcbbc.webp b/backend/uploads/images/ef259753-b9ee-45b7-86fc-d4381b5bcbbc.webp new file mode 100644 index 0000000..d96b82f Binary files /dev/null and b/backend/uploads/images/ef259753-b9ee-45b7-86fc-d4381b5bcbbc.webp differ diff --git a/backend/uploads/images/efcbe90e-8490-44f0-85f2-de24246ae89b.webp b/backend/uploads/images/efcbe90e-8490-44f0-85f2-de24246ae89b.webp new file mode 100644 index 0000000..71df0f0 Binary files /dev/null and b/backend/uploads/images/efcbe90e-8490-44f0-85f2-de24246ae89b.webp differ diff --git a/backend/uploads/images/effd7d2d-36c3-4840-99f8-5c4784c9b4c4.webp b/backend/uploads/images/effd7d2d-36c3-4840-99f8-5c4784c9b4c4.webp new file mode 100644 index 0000000..fa00bdf Binary files /dev/null and b/backend/uploads/images/effd7d2d-36c3-4840-99f8-5c4784c9b4c4.webp differ diff --git a/backend/uploads/images/f048fa0d-8191-46e7-95ca-d5f56316e85d.webp b/backend/uploads/images/f048fa0d-8191-46e7-95ca-d5f56316e85d.webp new file mode 100644 index 0000000..41def1a Binary files /dev/null and b/backend/uploads/images/f048fa0d-8191-46e7-95ca-d5f56316e85d.webp differ diff --git a/backend/uploads/images/f051ce49-76bc-4e36-a764-1f2c9bde67f6.webp b/backend/uploads/images/f051ce49-76bc-4e36-a764-1f2c9bde67f6.webp new file mode 100644 index 0000000..77dd3e2 Binary files /dev/null and b/backend/uploads/images/f051ce49-76bc-4e36-a764-1f2c9bde67f6.webp differ diff --git a/backend/uploads/images/f05f8722-7f2c-4adf-bdff-6db1269d0d0b.webp b/backend/uploads/images/f05f8722-7f2c-4adf-bdff-6db1269d0d0b.webp new file mode 100644 index 0000000..b2ec264 Binary files /dev/null and b/backend/uploads/images/f05f8722-7f2c-4adf-bdff-6db1269d0d0b.webp differ diff --git a/backend/uploads/images/f0610c80-11a4-45c0-a58a-dde311f4da4f.webp b/backend/uploads/images/f0610c80-11a4-45c0-a58a-dde311f4da4f.webp new file mode 100644 index 0000000..e7e6fca Binary files /dev/null and b/backend/uploads/images/f0610c80-11a4-45c0-a58a-dde311f4da4f.webp differ diff --git a/backend/uploads/images/f1126ed2-1588-4c09-8772-9288e0553bc7.webp b/backend/uploads/images/f1126ed2-1588-4c09-8772-9288e0553bc7.webp new file mode 100644 index 0000000..b48fe6a Binary files /dev/null and b/backend/uploads/images/f1126ed2-1588-4c09-8772-9288e0553bc7.webp differ diff --git a/backend/uploads/images/f1c1f3bb-20d5-4b48-8394-dbbe512e73ef.webp b/backend/uploads/images/f1c1f3bb-20d5-4b48-8394-dbbe512e73ef.webp new file mode 100644 index 0000000..8d8b16f Binary files /dev/null and b/backend/uploads/images/f1c1f3bb-20d5-4b48-8394-dbbe512e73ef.webp differ diff --git a/backend/uploads/images/f1d8e34d-7abb-4a40-b577-202a239e63e5.webp b/backend/uploads/images/f1d8e34d-7abb-4a40-b577-202a239e63e5.webp new file mode 100644 index 0000000..c2bca8c Binary files /dev/null and b/backend/uploads/images/f1d8e34d-7abb-4a40-b577-202a239e63e5.webp differ diff --git a/backend/uploads/images/f2666fe8-6d3b-49bd-aa4e-b9169b1f8bb0.webp b/backend/uploads/images/f2666fe8-6d3b-49bd-aa4e-b9169b1f8bb0.webp new file mode 100644 index 0000000..3952cd0 Binary files /dev/null and b/backend/uploads/images/f2666fe8-6d3b-49bd-aa4e-b9169b1f8bb0.webp differ diff --git a/backend/uploads/images/f29420ea-52c0-4851-af32-a558728518b8.webp b/backend/uploads/images/f29420ea-52c0-4851-af32-a558728518b8.webp new file mode 100644 index 0000000..6890413 Binary files /dev/null and b/backend/uploads/images/f29420ea-52c0-4851-af32-a558728518b8.webp differ diff --git a/backend/uploads/images/f2efb441-9162-4882-a9ec-41de556af2fd.webp b/backend/uploads/images/f2efb441-9162-4882-a9ec-41de556af2fd.webp new file mode 100644 index 0000000..ef711c2 Binary files /dev/null and b/backend/uploads/images/f2efb441-9162-4882-a9ec-41de556af2fd.webp differ diff --git a/backend/uploads/images/f3d993e9-33d8-42e1-b8ed-2ad7e39b2803.webp b/backend/uploads/images/f3d993e9-33d8-42e1-b8ed-2ad7e39b2803.webp new file mode 100644 index 0000000..6d9b28c Binary files /dev/null and b/backend/uploads/images/f3d993e9-33d8-42e1-b8ed-2ad7e39b2803.webp differ diff --git a/backend/uploads/images/f3faa263-70f0-465e-a1d4-4d440b0cfb48.webp b/backend/uploads/images/f3faa263-70f0-465e-a1d4-4d440b0cfb48.webp new file mode 100644 index 0000000..12b4d00 Binary files /dev/null and b/backend/uploads/images/f3faa263-70f0-465e-a1d4-4d440b0cfb48.webp differ diff --git a/backend/uploads/images/f419fdb2-46d4-4609-ada1-8d8c360816db.webp b/backend/uploads/images/f419fdb2-46d4-4609-ada1-8d8c360816db.webp new file mode 100644 index 0000000..01374fb Binary files /dev/null and b/backend/uploads/images/f419fdb2-46d4-4609-ada1-8d8c360816db.webp differ diff --git a/backend/uploads/images/f52cf739-2168-4cf1-b8f3-32bc3edf0df9.webp b/backend/uploads/images/f52cf739-2168-4cf1-b8f3-32bc3edf0df9.webp new file mode 100644 index 0000000..5e714d3 Binary files /dev/null and b/backend/uploads/images/f52cf739-2168-4cf1-b8f3-32bc3edf0df9.webp differ diff --git a/backend/uploads/images/f5521144-7391-42eb-99d3-e22b74c56382.webp b/backend/uploads/images/f5521144-7391-42eb-99d3-e22b74c56382.webp new file mode 100644 index 0000000..b4223ce Binary files /dev/null and b/backend/uploads/images/f5521144-7391-42eb-99d3-e22b74c56382.webp differ diff --git a/backend/uploads/images/f5badff7-46e5-4e7f-97ef-1f5d4ffaa9f5.webp b/backend/uploads/images/f5badff7-46e5-4e7f-97ef-1f5d4ffaa9f5.webp new file mode 100644 index 0000000..2e1de4a Binary files /dev/null and b/backend/uploads/images/f5badff7-46e5-4e7f-97ef-1f5d4ffaa9f5.webp differ diff --git a/backend/uploads/images/f7044e32-6ac4-4516-852e-edc1eb661211.webp b/backend/uploads/images/f7044e32-6ac4-4516-852e-edc1eb661211.webp new file mode 100644 index 0000000..15349cb Binary files /dev/null and b/backend/uploads/images/f7044e32-6ac4-4516-852e-edc1eb661211.webp differ diff --git a/backend/uploads/images/f7e42053-8cf2-4b65-976e-0066ddd7ce23.webp b/backend/uploads/images/f7e42053-8cf2-4b65-976e-0066ddd7ce23.webp new file mode 100644 index 0000000..b50055f Binary files /dev/null and b/backend/uploads/images/f7e42053-8cf2-4b65-976e-0066ddd7ce23.webp differ diff --git a/backend/uploads/images/f88d3876-085b-4c6c-b9c7-93c7eed6b872.webp b/backend/uploads/images/f88d3876-085b-4c6c-b9c7-93c7eed6b872.webp new file mode 100644 index 0000000..feae8ce Binary files /dev/null and b/backend/uploads/images/f88d3876-085b-4c6c-b9c7-93c7eed6b872.webp differ diff --git a/backend/uploads/images/f9375315-8b4e-4a54-97eb-1d36cd396667.webp b/backend/uploads/images/f9375315-8b4e-4a54-97eb-1d36cd396667.webp new file mode 100644 index 0000000..b94bfdd Binary files /dev/null and b/backend/uploads/images/f9375315-8b4e-4a54-97eb-1d36cd396667.webp differ diff --git a/backend/uploads/images/f94304a7-5fff-4286-a051-ba14c8ecc81f.webp b/backend/uploads/images/f94304a7-5fff-4286-a051-ba14c8ecc81f.webp new file mode 100644 index 0000000..fb61d7c Binary files /dev/null and b/backend/uploads/images/f94304a7-5fff-4286-a051-ba14c8ecc81f.webp differ diff --git a/backend/uploads/images/f9a40bde-2dd0-4fbf-874f-0f3a0bb1ec3a.webp b/backend/uploads/images/f9a40bde-2dd0-4fbf-874f-0f3a0bb1ec3a.webp new file mode 100644 index 0000000..cbc9332 Binary files /dev/null and b/backend/uploads/images/f9a40bde-2dd0-4fbf-874f-0f3a0bb1ec3a.webp differ diff --git a/backend/uploads/images/f9b0b302-7918-4dc6-8d07-0318d879aa64.webp b/backend/uploads/images/f9b0b302-7918-4dc6-8d07-0318d879aa64.webp new file mode 100644 index 0000000..586b62a Binary files /dev/null and b/backend/uploads/images/f9b0b302-7918-4dc6-8d07-0318d879aa64.webp differ diff --git a/backend/uploads/images/fa2eba7f-46f5-4e6a-b303-b4f87767562c.webp b/backend/uploads/images/fa2eba7f-46f5-4e6a-b303-b4f87767562c.webp new file mode 100644 index 0000000..fe9d44c Binary files /dev/null and b/backend/uploads/images/fa2eba7f-46f5-4e6a-b303-b4f87767562c.webp differ diff --git a/backend/uploads/images/fa324f1d-b58f-4646-acd8-53b42fa561b0.webp b/backend/uploads/images/fa324f1d-b58f-4646-acd8-53b42fa561b0.webp new file mode 100644 index 0000000..812b14f Binary files /dev/null and b/backend/uploads/images/fa324f1d-b58f-4646-acd8-53b42fa561b0.webp differ diff --git a/backend/uploads/images/fa50cf27-3181-4c15-b42e-9f88a7bd34f7.webp b/backend/uploads/images/fa50cf27-3181-4c15-b42e-9f88a7bd34f7.webp new file mode 100644 index 0000000..d171a19 Binary files /dev/null and b/backend/uploads/images/fa50cf27-3181-4c15-b42e-9f88a7bd34f7.webp differ diff --git a/backend/uploads/images/fa62e39f-2b14-47ce-b735-4441dce2f57f.webp b/backend/uploads/images/fa62e39f-2b14-47ce-b735-4441dce2f57f.webp new file mode 100644 index 0000000..68e16e8 Binary files /dev/null and b/backend/uploads/images/fa62e39f-2b14-47ce-b735-4441dce2f57f.webp differ diff --git a/backend/uploads/images/faaa93b7-6605-4b26-953a-adcd7c416c37.webp b/backend/uploads/images/faaa93b7-6605-4b26-953a-adcd7c416c37.webp new file mode 100644 index 0000000..7010d66 Binary files /dev/null and b/backend/uploads/images/faaa93b7-6605-4b26-953a-adcd7c416c37.webp differ diff --git a/backend/uploads/images/fadd136a-0cc0-48d8-a34c-5ed5dcba0563.webp b/backend/uploads/images/fadd136a-0cc0-48d8-a34c-5ed5dcba0563.webp new file mode 100644 index 0000000..f4de349 Binary files /dev/null and b/backend/uploads/images/fadd136a-0cc0-48d8-a34c-5ed5dcba0563.webp differ diff --git a/backend/uploads/images/fadda920-57fe-4214-a913-4370413f16ca.webp b/backend/uploads/images/fadda920-57fe-4214-a913-4370413f16ca.webp new file mode 100644 index 0000000..47946c8 Binary files /dev/null and b/backend/uploads/images/fadda920-57fe-4214-a913-4370413f16ca.webp differ diff --git a/backend/uploads/images/fb01af60-7656-40e7-b0bb-c916d92c9923.webp b/backend/uploads/images/fb01af60-7656-40e7-b0bb-c916d92c9923.webp new file mode 100644 index 0000000..1098514 Binary files /dev/null and b/backend/uploads/images/fb01af60-7656-40e7-b0bb-c916d92c9923.webp differ diff --git a/backend/uploads/images/fb08ef9e-6945-4237-9edf-49ea64c49900.webp b/backend/uploads/images/fb08ef9e-6945-4237-9edf-49ea64c49900.webp new file mode 100644 index 0000000..43c2db9 Binary files /dev/null and b/backend/uploads/images/fb08ef9e-6945-4237-9edf-49ea64c49900.webp differ diff --git a/backend/uploads/images/fb677025-6902-4761-a469-860ccf2e0063.webp b/backend/uploads/images/fb677025-6902-4761-a469-860ccf2e0063.webp new file mode 100644 index 0000000..8c3413e Binary files /dev/null and b/backend/uploads/images/fb677025-6902-4761-a469-860ccf2e0063.webp differ diff --git a/backend/uploads/images/fc5a4320-95ec-42f7-a226-f0b8a49a4dee.webp b/backend/uploads/images/fc5a4320-95ec-42f7-a226-f0b8a49a4dee.webp new file mode 100644 index 0000000..2a551a7 Binary files /dev/null and b/backend/uploads/images/fc5a4320-95ec-42f7-a226-f0b8a49a4dee.webp differ diff --git a/backend/uploads/images/fcd6f813-f6a4-40ba-a5f9-91c2aa0d4797.webp b/backend/uploads/images/fcd6f813-f6a4-40ba-a5f9-91c2aa0d4797.webp new file mode 100644 index 0000000..0892216 Binary files /dev/null and b/backend/uploads/images/fcd6f813-f6a4-40ba-a5f9-91c2aa0d4797.webp differ diff --git a/backend/uploads/images/fe852018-a8f5-4f5e-87cc-a3744485aab3.webp b/backend/uploads/images/fe852018-a8f5-4f5e-87cc-a3744485aab3.webp new file mode 100644 index 0000000..8f3930d Binary files /dev/null and b/backend/uploads/images/fe852018-a8f5-4f5e-87cc-a3744485aab3.webp differ diff --git a/backend/uploads/images/fef4d38f-2f1b-4897-b000-90d4aeea9853.webp b/backend/uploads/images/fef4d38f-2f1b-4897-b000-90d4aeea9853.webp new file mode 100644 index 0000000..1632290 Binary files /dev/null and b/backend/uploads/images/fef4d38f-2f1b-4897-b000-90d4aeea9853.webp differ diff --git a/backend/uploads/images/fef8a2e4-1df4-4684-8428-73de5fc44ffd.webp b/backend/uploads/images/fef8a2e4-1df4-4684-8428-73de5fc44ffd.webp new file mode 100644 index 0000000..02deff1 Binary files /dev/null and b/backend/uploads/images/fef8a2e4-1df4-4684-8428-73de5fc44ffd.webp differ diff --git a/debug_html.txt b/debug_html.txt new file mode 100644 index 0000000..849d037 --- /dev/null +++ b/debug_html.txt @@ -0,0 +1,2178 @@ + + + + + + + 买日本烟 花店 - ribenyan.com + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ +
+ +
+ +
+
+
+

+ +

+ +
+ + + +
+

+ +

+ +
+ + + +
+
+
+
+
+ +
+
+
+
+ ... +
+
+ 2940 +
+
+
+
+

+ Golden Virginia 50g装 +

+

+ ゴールデン・バージニア(50g) +

+
+ + + 本款 + + 无 + + 整大包免服务费规则 +
+
+ + + + 限购 + + 5包,超出服务费上调 +
+
+
+
+
+

+ 1400 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 2326 +
+
+
+
+

+ TEREA 红宝石/梅子原味 Ruby Regular E +

+

+ テリア・ルビー・レギュラー E +

+

+ IQOS ILUMA 专用烟弹 +

+
+
+
+
+

+ 620 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 4486 +
+
+
+
+

+ Che/切格瓦拉 银色 25g装 +

+

+ チェ・シャグ・シルバー(25g) +

+

+ 带纸 +

+
+
+
+
+

+ 680 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 1041 +
+
+
+
+

+ 梅比乌斯/MEVIUS E系列 薄荷 紫色爆珠 5mg 长支 细支 +

+

+ メビウス・イーシリーズ・メンソールOPパープル5・100’sスリム +

+
+
+
+
+

+ 500 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 3795 +
+
+
+
+

+ 老船长/Ark Royal Paradise Tea Shag 30g装 +

+

+ アークローヤル・パラダイスティー・シャグ(30g) +

+

+ 带纸 +

+
+
+
+
+

+ 840 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 2318 +
+
+
+
+

+ TEREA 黑冰蓝莓 Black Purple Menthol E +

+

+ テリア・ブラック・パープル・メンソール E +

+

+ IQOS ILUMA 专用烟弹 +

+
+
+
+
+

+ 620 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 2089 +
+
+
+
+

+ 卡比龙/Cigaronne Exclusive 棕色 +

+

+ シガローネ・エクスクルーシブ・ブラウン +

+
+ + + 本款 + + 无 + + 整条免服务费规则 +
+
+
+
+
+

+ 1500 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 2322 +
+
+
+
+

+ TEREA 浓薄荷 Menthol E +

+

+ テリア・メンソール E +

+

+ IQOS ILUMA 专用烟弹 +

+
+
+
+
+

+ 620 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 2316 +
+
+
+
+

+ TEREA 黑冰青柠/黑柠檬 Black Yellow Menthol E +

+

+ テリア・ブラック・イエロー・メンソール E +

+

+ IQOS ILUMA 专用烟弹 +

+
+
+
+
+

+ 620 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 1422 +
+
+
+
+

+ 七星/SevenStars 碳滤 7mg 硬盒 +

+

+ セブンスター・7・ボックス +

+
+
+
+
+

+ 600 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 3741 +
+
+
+
+

+ 黑魔鬼/BlackDevil 原味 +

+

+ ブラックデビル・オリジナル +

+
+
+
+
+

+ 560 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 3797 +
+
+
+
+

+ 老船长/Ark Royal Wineberry Shag 30g装 +

+

+ アークローヤル・ワインベリー・シャグ(30g) +

+

+ 带纸 +

+
+
+
+
+

+ 840 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 2310 +
+
+
+
+

+ TEREA 水蜜桃/花香 Fusion Menthol E +

+

+ テリア・フュージョン・メンソール E +

+

+ IQOS ILUMA 专用烟弹 +

+
+
+
+
+

+ 620 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 2920 +
+
+
+
+

+ 万宝路/Marlboro 红 软盒 +

+

+ マールボロ +

+
+
+
+
+

+ 620 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 2604 +
+
+
+
+

+ TEREA 青提爆珠 Shine Pearl +

+

+ テリア・シャイン・パール +

+

+ IQOS ILUMA 专用烟弹 +

+
+
+
+
+

+ 620 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 4430 +
+
+
+
+

+ Pepe Rich Green 30g装 +

+

+ ペペ・リッチグリーン(30g) +

+

+ 带纸 +

+
+
+
+
+

+ 800 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 1706 +
+
+
+
+

+ 梅比乌斯/MEVIUS Premium薄荷 黄色爆珠 5mg 硬盒 +

+

+ メビウス・プレミアムメンソール・オプション・イエロー・5 +

+
+
+
+
+

+ 580 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ T580 +
+
+
+
+

+ 卡比龙/Cigaronne Super Slims 薄荷 +

+

+ シガローネ スーパースリム・メンソール +

+
+ + + 本款 + + 无 + + 整条免服务费规则 +
+
+
+
+
+

+ 780 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 2303 +
+
+
+
+

+ TEREA 坚果 Smooth Regular E +

+

+ テリア・スムース・レギュラー E +

+

+ IQOS ILUMA 专用烟弹 +

+
+
+
+
+

+ 620 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+
+ ... +
+
+ 2921 +
+
+
+
+

+ 万宝路/Marlboro 红 硬盒 +

+

+ マールボロ・ボックス +

+
+
+
+
+

+ 620 + + 日元 + + + /包 + +

+
+
+ + 加购物车 + +
+ + + 1 + + +
+
+
+
+
+
+
+
+ + +
+ + + + 0 + +
+
+ + + + + + +
+ +
+ + + + + + +
+ + diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..b8bc810 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + Sale + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..ebe80f3 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,2383 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "@vitejs/plugin-vue": "^6.0.5", + "axios": "^1.15.0", + "element-plus": "^2.13.6", + "pinia": "^3.0.4", + "sass": "^1.99.0", + "vue-i18n": "^9.14.5", + "vue-router": "^4.6.4" + }, + "devDependencies": { + "typescript": "~6.0.2", + "vite": "^6.4.2" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@intlify/core-base": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz", + "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==", + "license": "MIT", + "dependencies": { + "@intlify/message-compiler": "9.14.5", + "@intlify/shared": "9.14.5" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/message-compiler": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz", + "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==", + "license": "MIT", + "dependencies": { + "@intlify/shared": "9.14.5", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@intlify/shared": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz", + "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.5.tgz", + "integrity": "sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==", + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.2" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.32.tgz", + "integrity": "sha512-4x74Tbtqnda8s/NSD6e1Dr5p1c8HdMU5RWSjMSUzb8RTcUQqevDCxVAitcLBKT+ie3o0Dl9crc/S/opJM7qBGQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/shared": "3.5.32", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.32.tgz", + "integrity": "sha512-ybHAu70NtiEI1fvAUz3oXZqkUYEe5J98GjMDpTGl5iHb0T15wQYLR4wE3h9xfuTNA+Cm2f4czfe8B4s+CCH57Q==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.32", + "@vue/shared": "3.5.32" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.32.tgz", + "integrity": "sha512-8UYUYo71cP/0YHMO814TRZlPuUUw3oifHuMR7Wp9SNoRSrxRQnhMLNlCeaODNn6kNTJsjFoQ/kqIj4qGvya4Xg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/compiler-core": "3.5.32", + "@vue/compiler-dom": "3.5.32", + "@vue/compiler-ssr": "3.5.32", + "@vue/shared": "3.5.32", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.8", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.32.tgz", + "integrity": "sha512-Gp4gTs22T3DgRotZ8aA/6m2jMR+GMztvBXUBEUOYOcST+giyGWJ4WvFd7QLHBkzTxkfOt8IELKNdpzITLbA2rw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.32", + "@vue/shared": "3.5.32" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.32.tgz", + "integrity": "sha512-/ORasxSGvZ6MN5gc+uE364SxFdJ0+WqVG0CENXaGW58TOCdrAW76WWaplDtECeS1qphvtBZtR+3/o1g1zL4xPQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.32" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.32.tgz", + "integrity": "sha512-pDrXCejn4UpFDFmMd27AcJEbHaLemaE5o4pbb7sLk79SRIhc6/t34BQA7SGNgYtbMnvbF/HHOftYBgFJtUoJUQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.32", + "@vue/shared": "3.5.32" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.32.tgz", + "integrity": "sha512-1CDVv7tv/IV13V8Nip1k/aaObVbWqRlVCVezTwx3K07p7Vxossp5JU1dcPNhJk3w347gonIUT9jQOGutyJrSVQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.32", + "@vue/runtime-core": "3.5.32", + "@vue/shared": "3.5.32", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.32.tgz", + "integrity": "sha512-IOjm2+JQwRFS7W28HNuJeXQle9KdZbODFY7hFGVtnnghF51ta20EWAZJHX+zLGtsHhaU6uC9BGPV52KVpYryMQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.32", + "@vue/shared": "3.5.32" + }, + "peerDependencies": { + "vue": "3.5.32" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.32.tgz", + "integrity": "sha512-ksNyrmRQzWJJ8n3cRDuSF7zNNontuJg1YHnmWRJd2AMu8Ij2bqwiiri2lH5rHtYPZjj4STkNcgcmiQqlOjiYGg==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.0.0.tgz", + "integrity": "sha512-C12RukhXiJCbx4MGhjmd/gH52TjJsc3G0E0kQj/kb19H3Nt6n1CA4DRWuTdWWcaFRdlTe0npWDS942mvacvNBw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "12.0.0", + "@vueuse/shared": "12.0.0", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.0.0.tgz", + "integrity": "sha512-Yzimd1D3sjxTDOlF05HekU5aSGdKjxhuhRFHA7gDWLn57PRbBIh+SF5NmjhJ0WRgF3my7T8LBucyxdFJjIfRJQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.0.0.tgz", + "integrity": "sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==", + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", + "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/element-plus": { + "version": "2.13.6", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.13.6.tgz", + "integrity": "sha512-XHgwXr8Fjz6i+6BaqFhAbae/dJbG7bBAAlHrY3pWL7dpj+JcqcOyKYt4Oy5KP86FQwS1k4uIZDjCx2FyUR5lDg==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.2.0", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.0.1", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.7", + "@types/lodash": "^4.17.20", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "12.0.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.19", + "lodash": "^4.17.23", + "lodash-es": "^4.17.23", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0", + "vue-component-type-helpers": "^3.2.4" + }, + "peerDependencies": { + "vue": "^3.3.0" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "license": "MIT" + }, + "node_modules/immutable": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.5.tgz", + "integrity": "sha512-t7xcm2siw+hlUM68I+UEOK+z84RzmN59as9DZ7P1l0994DKUWV7UXBMQZVxaoMSRQ+PBZbHCOoBt7a2wxOMt+A==", + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pinia": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", + "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^7.7.7" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.5.0", + "vue": "^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/sass": { + "version": "1.99.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.99.0.tgz", + "integrity": "sha512-kgW13M54DUB7IsIRM5LvJkNlpH+WhMpooUcaWGFARkF1Tc82v9mIWkCbCYf+MBvpIUBSeSOTilpZjEPr2VYE6Q==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.1.5", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.32", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.32.tgz", + "integrity": "sha512-vM4z4Q9tTafVfMAK7IVzmxg34rSzTFMyIe0UUEijUCkn9+23lj0WRfA83dg7eQZIUlgOSGrkViIaCfqSAUXsMw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.32", + "@vue/compiler-sfc": "3.5.32", + "@vue/runtime-dom": "3.5.32", + "@vue/server-renderer": "3.5.32", + "@vue/shared": "3.5.32" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.2.6.tgz", + "integrity": "sha512-O02tnvIfOQVmnvoWwuSydwRoHjZVt8UEBR+2p4rT35p8GAy5VTlWP8o5qXfJR/GWCN0nVZoYWsVUvx2jwgdBmQ==", + "license": "MIT" + }, + "node_modules/vue-i18n": { + "version": "9.14.5", + "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz", + "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==", + "deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html", + "license": "MIT", + "dependencies": { + "@intlify/core-base": "9.14.5", + "@intlify/shared": "9.14.5", + "@vue/devtools-api": "^6.5.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/kazupon" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/vue-i18n/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-router/node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..797c193 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,25 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "typescript": "~6.0.2", + "vite": "^6.4.2" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "@vitejs/plugin-vue": "^6.0.5", + "axios": "^1.15.0", + "element-plus": "^2.13.6", + "pinia": "^3.0.4", + "sass": "^1.99.0", + "vue-i18n": "^9.14.5", + "vue-router": "^4.6.4" + } +} diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..98240ae --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,3 @@ + diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts new file mode 100644 index 0000000..4be5c4d --- /dev/null +++ b/frontend/src/api/index.ts @@ -0,0 +1,162 @@ +import api from '../utils/request' + +export const uploadApi = { + uploadImage: (file: File) => { + const formData = new FormData() + formData.append('file', file) + return api.post('/upload', formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }) + }, + uploadMultiple: (files: File[]) => { + const formData = new FormData() + files.forEach(file => formData.append('files', file)) + return api.post('/upload/multiple', formData, { + headers: { 'Content-Type': 'multipart/form-data' } + }) + }, + deleteImage: (filename: string) => api.delete(`/upload/${filename}`) +} + +export const systemApi = { + checkInstalled: () => api.get('/installed'), + install: (data: any) => api.post('/install', data), + getPublicSettings: () => api.get('/settings/public'), +} + +export const authApi = { + register: (data: any) => api.post('/auth/register', data), + login: (data: any) => api.post('/auth/login', data), + forgotPassword: (data: any) => api.post('/auth/forgot-password', data), + resetPassword: (data: any) => api.post('/auth/reset-password', data), + verifyEmail: (params: any) => api.get('/auth/verify-email', { params }), + sendVerifyCode: () => api.post('/users/send-verify-code'), +} + +export const userApi = { + getProfile: () => api.get('/users/profile'), + updateProfile: (data: any) => api.put('/users/profile', data), + changePassword: (data: any) => api.put('/users/password', data), +} + +export const addressApi = { + list: () => api.get('/users/addresses'), + create: (data: any) => api.post('/users/addresses', data), + update: (id: number, data: any) => api.put(`/users/addresses/${id}`, data), + delete: (id: number) => api.delete(`/users/addresses/${id}`), + setDefault: (id: number) => api.put(`/users/addresses/${id}/default`), +} + +export const categoryApi = { + list: () => api.get('/categories'), +} + +export const brandApi = { + list: () => api.get('/brands'), +} + +export const productApi = { + list: (params?: any) => api.get('/products', { params }), + getById: (id: number) => api.get(`/products/${id}`), +} + +export const cartApi = { + list: () => api.get('/cart'), + add: (data: any) => api.post('/cart', data), + update: (id: number, data: any) => api.put(`/cart/${id}`, data), + delete: (id: number) => api.delete(`/cart/${id}`), +} + +export const orderApi = { + list: () => api.get('/orders'), + getById: (id: number) => api.get(`/orders/${id}`), + create: (data: any) => api.post('/orders', data), + refund: (id: number, data: any) => api.post(`/orders/${id}/refund`, data), +} + +export const articleApi = { + list: (params?: any) => api.get('/articles', { params }), + getById: (id: number) => api.get(`/articles/${id}`), +} + +export const lotteryApi = { + list: () => api.get('/lotteries'), + getById: (id: number) => api.get(`/lotteries/${id}`), + register: (id: number) => api.post(`/lotteries/${id}/register`), +} + +export const ticketApi = { + list: (params?: any) => api.get('/tickets', { params }), + create: (data: any) => api.post('/tickets', data), + getById: (id: number) => api.get(`/tickets/${id}`), +} + +export const menuApi = { + list: () => api.get('/menus'), +} + +export const supplierApi = { + getOrders: () => api.get('/supplier/orders'), + getOrderById: (id: number) => api.get(`/supplier/orders/${id}`), + confirmOrder: (id: number) => api.put(`/supplier/orders/${id}/confirm`), + shipOrder: (id: number, data: any) => api.put(`/supplier/orders/${id}/ship`, data), + getInventory: () => api.get('/supplier/inventory'), + updateInventory: (id: number, data: any) => api.put(`/supplier/inventory/${id}`, data), +} + +export const adminApi = { + getCategories: () => api.get('/categories'), + createCategory: (data: any) => api.post('/admin/categories', data), + updateCategory: (id: number, data: any) => api.put(`/admin/categories/${id}`, data), + deleteCategory: (id: number) => api.delete(`/admin/categories/${id}`), + + getBrands: () => api.get('/admin/brands'), + createBrand: (data: any) => api.post('/admin/brands', data), + updateBrand: (id: number, data: any) => api.put(`/admin/brands/${id}`, data), + deleteBrand: (id: number) => api.delete(`/admin/brands/${id}`), + + getProducts: () => api.get('/admin/products'), + createProduct: (data: any) => api.post('/admin/products', data), + updateProduct: (id: number, data: any) => api.put(`/admin/products/${id}`, data), + deleteProduct: (id: number) => api.delete(`/admin/products/${id}`), + addCustomField: (id: number, data: any) => api.post(`/admin/products/${id}/custom-fields`, data), + + getOrders: (params?: any) => api.get('/admin/orders', { params }), + exportOrders: () => api.get('/admin/orders/export'), + processRefund: (id: number, data: any) => api.put(`/admin/orders/${id}/refund`, data), + + getSuppliers: () => api.get('/admin/suppliers'), + createSupplier: (data: any) => api.post('/admin/suppliers', data), + updateSupplier: (id: number, data: any) => api.put(`/admin/suppliers/${id}`, data), + deleteSupplier: (id: number) => api.delete(`/admin/suppliers/${id}`), + authorizeSupplier: (id: number, data: any) => api.post(`/admin/suppliers/${id}/authorize`, data), + + getLotteries: () => api.get('/lotteries'), + createLottery: (data: any) => api.post('/admin/lotteries', data), + updateLottery: (id: number, data: any) => api.put(`/admin/lotteries/${id}`, data), + deleteLottery: (id: number) => api.delete(`/admin/lotteries/${id}`), + addLotteryPrize: (id: number, data: any) => api.post(`/admin/lotteries/${id}/prizes`, data), + drawLottery: (id: number) => api.post(`/admin/lotteries/${id}/draw`), + + getTickets: (params?: any) => api.get('/admin/tickets', { params }), + updateTicket: (id: number, data: any) => api.put(`/admin/tickets/${id}`, data), + assignTicket: (id: number, data: any) => api.put(`/admin/tickets/${id}/assign`, data), + + getSettings: () => api.get('/admin/settings'), + updateSettings: (data: any) => api.put('/admin/settings', data), + updateSMTP: (data: any) => api.put('/admin/settings/smtp', data), + updatePayment: (data: any) => api.put('/admin/settings/payment', data), + + getMenus: () => api.get('/admin/menus'), + createMenu: (data: any) => api.post('/admin/menus', data), + updateMenu: (id: number, data: any) => api.put(`/admin/menus/${id}`, data), + deleteMenu: (id: number) => api.delete(`/admin/menus/${id}`), + + getInventory: () => api.get('/admin/inventory'), + + getArticles: (params?: any) => api.get('/admin/articles', { params }), + createArticle: (data: any) => api.post('/admin/articles', data), + updateArticle: (id: number, data: any) => api.put(`/admin/articles/${id}`, data), + deleteArticle: (id: number) => api.delete(`/admin/articles/${id}`), + togglePinArticle: (id: number) => api.put(`/admin/articles/${id}/pin`), +} diff --git a/frontend/src/assets/hero.png b/frontend/src/assets/hero.png new file mode 100644 index 0000000..cc51a3d Binary files /dev/null and b/frontend/src/assets/hero.png differ diff --git a/frontend/src/assets/styles/global.scss b/frontend/src/assets/styles/global.scss new file mode 100644 index 0000000..b00724b --- /dev/null +++ b/frontend/src/assets/styles/global.scss @@ -0,0 +1,1521 @@ +:root { + --el-color-primary: #4e6ef2; + --el-color-primary-light-3: #6b85f5; + --el-color-primary-light-5: #8da2f7; + --el-color-primary-light-7: #afbff9; + --el-color-primary-light-8: #c5d1fb; + --el-color-primary-light-9: #dce3fd; + --el-color-primary-dark-2: #3d5ad0; + + --el-bg-color: #1a1a2e; + --el-bg-color-overlay: #2d2d44; + --el-text-color-primary: rgba(255, 255, 255, 0.85); + --el-text-color-regular: rgba(255, 255, 255, 0.75); + --el-text-color-secondary: rgba(255, 255, 255, 0.55); + --el-text-color-placeholder: rgba(255, 255, 255, 0.4); + --el-border-color: rgba(255, 255, 255, 0.08); + --el-border-color-light: rgba(255, 255, 255, 0.06); + --el-border-color-lighter: rgba(255, 255, 255, 0.04); + --el-fill-color: rgba(255, 255, 255, 0.06); + --el-fill-color-light: rgba(255, 255, 255, 0.04); + --el-fill-color-lighter: rgba(255, 255, 255, 0.02); + --el-fill-color-blank: transparent; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + color: rgba(255, 255, 255, 0.85); + background: #1a1a2e; + min-height: 100vh; + -webkit-font-smoothing: antialiased; +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.02); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; + + &:hover { + background: rgba(255, 255, 255, 0.2); + } +} + +::-webkit-scrollbar-corner { + background: transparent; +} + +a { + color: #4e6ef2; + text-decoration: none; + transition: color 0.15s; +} + +a:hover { + color: #6b85f5; +} + +h1, h2, h3, h4, h5, h6 { + color: #fff; +} + +p { + color: rgba(255, 255, 255, 0.55); +} + +.page-container { + max-width: 900px; + margin: 0 auto; + padding: 24px; +} + +.auth-page { + min-height: 80vh; + display: flex; + align-items: center; + justify-content: center; + padding: 40px 20px; +} + +.auth-card { + background: #2d2d44; + border: 1px solid rgba(255, 255, 255, 0.06); + border-radius: 12px; + padding: 36px; + width: 100%; + max-width: 400px; +} + +.auth-card h2 { + color: #fff; + text-align: center; + margin-bottom: 24px; + font-size: 20px; + font-weight: 600; +} + +.auth-links { + display: flex; + justify-content: space-between; + margin-top: 16px; +} + +.auth-links a { + color: rgba(255, 255, 255, 0.4); + font-size: 13px; +} + +.auth-links a:hover { + color: #4e6ef2; +} + +.el-button--primary { + background: #4e6ef2 !important; + border: none !important; + box-shadow: none !important; + color: #fff !important; +} + +.el-button--primary:hover { + background: #5a7af5 !important; + color: #fff !important; +} + +.el-button--primary.is-link { + background: transparent !important; + color: #4e6ef2 !important; +} + +.el-button--primary.is-link:hover { + color: #6b85f5 !important; +} + +.el-button--default { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + color: rgba(255, 255, 255, 0.8) !important; +} + +.el-button--default:hover, +.el-button--default:focus, +.el-button--default:active { + background: #35355a !important; + border-color: rgba(255, 255, 255, 0.2) !important; + color: #fff !important; +} + +.el-button:not(.el-button--primary):not(.el-button--success):not(.el-button--warning):not(.el-button--danger):not(.el-button--info) { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + color: rgba(255, 255, 255, 0.8) !important; +} + +.el-button:not(.el-button--primary):not(.el-button--success):not(.el-button--warning):not(.el-button--danger):not(.el-button--info):hover, +.el-button:not(.el-button--primary):not(.el-button--success):not(.el-button--warning):not(.el-button--danger):not(.el-button--info):focus, +.el-button:not(.el-button--primary):not(.el-button--success):not(.el-button--warning):not(.el-button--danger):not(.el-button--info):active { + background: #35355a !important; + border-color: rgba(255, 255, 255, 0.2) !important; + color: #fff !important; +} + +.el-dialog .el-button:not(.el-button--primary):not(.el-button--success):not(.el-button--warning):not(.el-button--danger) { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + color: rgba(255, 255, 255, 0.8) !important; +} + +.el-dialog .el-button:not(.el-button--primary):not(.el-button--success):not(.el-button--warning):not(.el-button--danger):hover, +.el-dialog .el-button:not(.el-button--primary):not(.el-button--success):not(.el-button--warning):not(.el-button--danger):focus { + background: #35355a !important; + border-color: rgba(255, 255, 255, 0.2) !important; + color: #fff !important; +} + +.el-input__wrapper { + background: rgba(255, 255, 255, 0.06) !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 8px !important; + box-shadow: none !important; +} + +.el-input__wrapper:hover { + border-color: rgba(255, 255, 255, 0.15) !important; +} + +.el-input__wrapper.is-focus { + border-color: #4e6ef2 !important; + box-shadow: 0 0 0 2px rgba(78, 110, 242, 0.15) !important; +} + +.el-input__inner { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-input__inner::placeholder { + color: rgba(255, 255, 255, 0.3) !important; +} + +.el-textarea__inner { + background: rgba(255, 255, 255, 0.06) !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 8px !important; + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-textarea__inner:focus { + border-color: #4e6ef2 !important; + box-shadow: 0 0 0 2px rgba(78, 110, 242, 0.15) !important; +} + +.el-select-dropdown { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 8px !important; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3) !important; +} + +.el-select-dropdown.el-popper { + background: #2d2d44 !important; +} + +.el-select-dropdown__wrap { + background: transparent !important; + max-height: 274px !important; +} + +.el-select-dropdown__list { + background: transparent !important; + padding: 4px 0 !important; + list-style: none !important; +} + +.el-select-dropdown__item { + color: rgba(255, 255, 255, 0.75) !important; + padding: 8px 16px !important; + background: transparent !important; + font-size: 14px !important; + line-height: 22px !important; + height: auto !important; + min-height: 38px !important; + display: flex !important; + align-items: center !important; +} + +.el-select-dropdown__item:hover, +.el-select-dropdown__item.hover { + background: rgba(255, 255, 255, 0.06) !important; + color: #fff !important; +} + +.el-select-dropdown__item.selected, +.el-select-dropdown__item.is-selected { + color: #4e6ef2 !important; + font-weight: 600 !important; + background: rgba(78, 110, 242, 0.15) !important; +} + +.el-select-dropdown__item.is-hovering { + background: rgba(255, 255, 255, 0.06) !important; + color: #fff !important; +} + +.el-select-dropdown__item.is-disabled { + color: rgba(255, 255, 255, 0.3) !important; + cursor: not-allowed !important; +} + +.el-select-dropdown__empty { + color: rgba(255, 255, 255, 0.35) !important; + padding: 10px 0 !important; + margin: 0 !important; + text-align: center !important; +} + +.dark-select-dropdown { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 8px !important; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3) !important; +} + +.dark-select-dropdown .el-select-dropdown__wrap { + background: transparent !important; + max-height: 274px !important; +} + +.dark-select-dropdown .el-select-dropdown__list { + background: transparent !important; + padding: 4px 0 !important; + list-style: none !important; + margin: 0 !important; +} + +.dark-select-dropdown .el-select-dropdown__item { + color: rgba(255, 255, 255, 0.75) !important; + padding: 0 16px !important; + background: transparent !important; + font-size: 14px !important; + line-height: 34px !important; + height: 34px !important; + display: flex !important; + align-items: center !important; + cursor: pointer !important; + margin: 0 !important; +} + +.dark-select-dropdown .el-select-dropdown__item:hover, +.dark-select-dropdown .el-select-dropdown__item.hover { + background: rgba(255, 255, 255, 0.06) !important; + color: #fff !important; +} + +.dark-select-dropdown .el-select-dropdown__item.selected, +.dark-select-dropdown .el-select-dropdown__item.is-selected { + color: #4e6ef2 !important; + font-weight: 600 !important; + background: rgba(78, 110, 242, 0.15) !important; +} + +.dark-select-dropdown .el-select-dropdown__item.is-disabled { + color: rgba(255, 255, 255, 0.3) !important; + cursor: not-allowed !important; +} + +.dark-select-dropdown .el-select-dropdown__empty { + color: rgba(255, 255, 255, 0.35) !important; + padding: 10px 0 !important; + margin: 0 !important; + text-align: center !important; +} + +.dark-select-dropdown.el-popper { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +.dark-date-picker { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 8px !important; + box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3) !important; +} + +.dark-date-picker.el-popper { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +.dark-date-picker .el-picker-panel { + background: #2d2d44 !important; + border: none !important; +} + +.dark-date-picker .el-date-picker__header-label { + color: rgba(255, 255, 255, 0.6) !important; +} + +.dark-date-picker .el-date-picker__header-label:hover { + color: #4e6ef2 !important; +} + +.dark-date-picker .el-date-table th { + color: rgba(255, 255, 255, 0.4) !important; +} + +.dark-date-picker .el-date-table td.available:hover { + color: #4e6ef2 !important; +} + +.dark-date-picker .el-date-table td.today span { + color: #4e6ef2 !important; +} + +.dark-date-picker .el-date-table td.current:not(.disabled) span { + background: #4e6ef2 !important; + color: #fff !important; +} + +.dark-date-picker .el-date-table td span { + color: rgba(255, 255, 255, 0.65) !important; +} + +.dark-date-picker .el-date-table td.prev-month span, +.dark-date-picker .el-date-table td.next-month span { + color: rgba(255, 255, 255, 0.25) !important; +} + +.dark-date-picker .el-picker-panel__icon-btn { + color: rgba(255, 255, 255, 0.5) !important; +} + +.dark-date-picker .el-picker-panel__icon-btn:hover { + color: #4e6ef2 !important; +} + +.dark-date-picker .el-month-table td .cell { + color: rgba(255, 255, 255, 0.6) !important; +} + +.dark-date-picker .el-month-table td .cell:hover { + color: #4e6ef2 !important; +} + +.dark-date-picker .el-year-table td .cell { + color: rgba(255, 255, 255, 0.6) !important; +} + +.dark-date-picker .el-year-table td .cell:hover { + color: #4e6ef2 !important; +} + +.dark-date-picker .el-time-panel { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +.dark-date-picker .el-time-spinner__item { + color: rgba(255, 255, 255, 0.5) !important; +} + +.dark-date-picker .el-time-spinner__item:hover, +.dark-date-picker .el-time-spinner__item.active { + color: #4e6ef2 !important; +} + +.dark-date-picker .el-time-panel__content { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-select .el-input__wrapper { + background: rgba(255, 255, 255, 0.06) !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 8px !important; + box-shadow: none !important; +} + +.el-select .el-input__wrapper:hover { + border-color: rgba(255, 255, 255, 0.15) !important; +} + +.el-select .el-input__wrapper.is-focus { + border-color: #4e6ef2 !important; + box-shadow: 0 0 0 2px rgba(78, 110, 242, 0.15) !important; +} + +.el-select .el-input__inner { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-select .el-input__inner::placeholder { + color: rgba(255, 255, 255, 0.4) !important; +} + +.el-select .el-input__suffix .el-icon { + color: rgba(255, 255, 255, 0.4) !important; +} + +.el-select .el-select__placeholder { + color: rgba(255, 255, 255, 0.4) !important; +} + +.el-select .el-select__selected-item { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-popper.is-light { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +.el-popper.is-dark { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-popper.is-pure { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +.el-popper[data-popper-placement^="bottom"] .el-popper__arrow::before { + background: #2d2d44 !important; + border-color: rgba(255, 255, 255, 0.08) !important; +} + +.el-popper[data-popper-placement^="top"] .el-popper__arrow::before { + background: #2d2d44 !important; + border-color: rgba(255, 255, 255, 0.08) !important; +} + +.el-dropdown__popper, +.el-tooltip__popper { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +.el-dropdown__popper.el-popper, +.el-tooltip__popper.el-popper { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +.el-dropdown__popper .el-dropdown-menu, +.el-tooltip__popper .el-dropdown-menu { + background: transparent !important; + border: none !important; +} + +.el-dropdown__popper[data-popper-placement^="bottom"] .el-popper__arrow::before, +.el-dropdown__popper[data-popper-placement^="top"] .el-popper__arrow::before, +.el-dropdown__popper[data-popper-placement^="left"] .el-popper__arrow::before, +.el-dropdown__popper[data-popper-placement^="right"] .el-popper__arrow::before { + background: #2d2d44 !important; + border-color: rgba(255, 255, 255, 0.08) !important; +} + +.el-tooltip__popper.is-dark { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-tooltip__popper.is-light { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-card { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.06) !important; + border-radius: 12px !important; +} + +.el-card__header { + border-bottom: 1px solid rgba(255, 255, 255, 0.06) !important; + color: #fff !important; +} + +.el-dropdown-menu { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 8px !important; + padding: 4px 0 !important; +} + +.el-dropdown-menu__item { + color: rgba(255, 255, 255, 0.75) !important; + padding: 8px 16px !important; + transition: all 0.15s !important; +} + +.el-dropdown-menu__item:hover, +.el-dropdown-menu__item:focus { + background: rgba(255, 255, 255, 0.06) !important; + color: #fff !important; + outline: none !important; +} + +.el-dropdown-menu__item.is-active { + color: #4e6ef2 !important; + background: rgba(78, 110, 242, 0.1) !important; +} + +.el-dropdown-menu__item--divided { + border-top: 1px solid rgba(255, 255, 255, 0.06) !important; + margin-top: 4px !important; + padding-top: 12px !important; +} + +.el-tag { + border: none !important; +} + +.el-tag--info { + background: rgba(255, 255, 255, 0.06) !important; + color: rgba(255, 255, 255, 0.55) !important; +} + +.el-tag--success { + background: rgba(16, 185, 129, 0.12) !important; + color: #10b981 !important; +} + +.el-tag--warning { + background: rgba(245, 158, 11, 0.12) !important; + color: #f59e0b !important; +} + +.el-tag--danger { + background: rgba(239, 68, 68, 0.12) !important; + color: #f87171 !important; +} + +.el-table { + --el-table-bg-color: transparent !important; + --el-table-tr-bg-color: transparent !important; + --el-table-header-bg-color: rgba(255, 255, 255, 0.03) !important; + --el-table-row-hover-bg-color: rgba(255, 255, 255, 0.04) !important; + --el-table-text-color: rgba(255, 255, 255, 0.75) !important; + --el-table-header-text-color: rgba(255, 255, 255, 0.45) !important; + --el-table-border-color: rgba(255, 255, 255, 0.06) !important; +} + +.el-button--danger { + background: #ef4444 !important; + border: none !important; + color: #fff !important; +} + +.el-button--danger:hover { + background: #f87171 !important; + color: #fff !important; +} + +.el-button--danger.is-link { + background: transparent !important; + color: #f87171 !important; +} + +.el-button--danger.is-link:hover { + color: #fca5a5 !important; +} + +.el-button--warning { + background: #f59e0b !important; + border: none !important; + color: #fff !important; +} + +.el-button--warning:hover { + background: #fbbf24 !important; + color: #fff !important; +} + +.el-button--warning.is-link { + background: transparent !important; + color: #f59e0b !important; +} + +.el-button--warning.is-link:hover { + color: #fbbf24 !important; +} + +.el-button--success { + background: #10b981 !important; + border: none !important; + color: #fff !important; +} + +.el-button--success:hover { + background: #34d399 !important; + color: #fff !important; +} + +.el-button--success.is-link { + background: transparent !important; + color: #10b981 !important; +} + +.el-button--success.is-link:hover { + color: #34d399 !important; +} + +.el-button--info { + background: rgba(255, 255, 255, 0.1) !important; + border: none !important; + color: rgba(255, 255, 255, 0.8) !important; +} + +.el-button--info:hover { + background: rgba(255, 255, 255, 0.15) !important; + color: #fff !important; +} + +.el-button--info.is-link { + background: transparent !important; + color: rgba(255, 255, 255, 0.6) !important; +} + +.el-button--info.is-link:hover { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-table .el-button.is-link { + padding: 4px 8px; + font-size: 13px; +} + +.el-table .el-button.is-link + .el-button.is-link { + margin-left: 8px; +} + +.el-pagination { + --el-pagination-bg-color: rgba(255, 255, 255, 0.04) !important; + --el-pagination-text-color: rgba(255, 255, 255, 0.55) !important; + --el-pagination-button-bg-color: #2d2d44 !important; + --el-pagination-hover-color: #4e6ef2 !important; +} + +.el-dialog { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 12px !important; +} + +.el-dialog__title { + color: #fff !important; +} + +.el-dialog__headerbtn .el-dialog__close { + color: rgba(255, 255, 255, 0.4) !important; +} + +.el-form-item__label { + color: rgba(255, 255, 255, 0.55) !important; +} + +.el-empty__description p { + color: rgba(255, 255, 255, 0.3) !important; +} + +.el-breadcrumb__inner { + color: rgba(255, 255, 255, 0.45) !important; +} + +.el-breadcrumb__inner:hover { + color: #4e6ef2 !important; +} + +.el-tabs__item { + color: rgba(255, 255, 255, 0.5) !important; +} + +.el-tabs__item:hover { + color: rgba(255, 255, 255, 0.8) !important; +} + +.el-tabs__item.is-active { + color: #4e6ef2 !important; +} + +.el-tabs__nav-wrap::after { + background-color: rgba(255, 255, 255, 0.06) !important; +} + +.el-divider { + border-color: rgba(255, 255, 255, 0.06) !important; +} + +.el-switch.is-checked .el-switch__core { + background-color: #4e6ef2 !important; + border-color: #4e6ef2 !important; +} + +.el-radio__input.is-checked .el-radio__inner { + background-color: #4e6ef2 !important; + border-color: #4e6ef2 !important; +} + +.el-radio__input.is-checked + .el-radio__label { + color: #4e6ef2 !important; +} + +.el-checkbox__input.is-checked .el-checkbox__inner { + background-color: #4e6ef2 !important; + border-color: #4e6ef2 !important; +} + +.el-checkbox__input.is-checked + .el-checkbox__label { + color: #4e6ef2 !important; +} + +.el-message-box { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 12px !important; +} + +.el-message-box__title { + color: #fff !important; +} + +.el-message-box__message { + color: rgba(255, 255, 255, 0.65) !important; +} + +.el-notification { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 10px !important; +} + +.el-notification__title { + color: #fff !important; +} + +.el-notification__content { + color: rgba(255, 255, 255, 0.55) !important; +} + +.el-menu { + background: #16162a !important; + border: none !important; +} + +.el-menu-item { + color: rgba(255, 255, 255, 0.55) !important; +} + +.el-menu-item:hover, +.el-menu-item.is-active { + background: rgba(78, 110, 242, 0.1) !important; + color: #4e6ef2 !important; +} + +.el-alert { + border-radius: 8px !important; +} + +.el-descriptions__label { + color: rgba(255, 255, 255, 0.45) !important; +} + +.el-descriptions__content { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-step__title { + color: rgba(255, 255, 255, 0.45) !important; +} + +.el-step__title.is-process { + color: #fff !important; +} + +.el-step__description { + color: rgba(255, 255, 255, 0.3) !important; +} + +.cart-item, +.cart-footer { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.06) !important; + box-shadow: none !important; +} + +.product-card-shared { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.06) !important; + box-shadow: none !important; +} + +.article-card { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.06) !important; + box-shadow: none !important; +} + +.product-card { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.06) !important; + box-shadow: none !important; +} + +.product-image { + background: rgba(255, 255, 255, 0.04) !important; +} + +.product-info h3 { + color: rgba(255, 255, 255, 0.9) !important; +} + +.product-info .price { + color: #4e6ef2 !important; +} + +.filter-bar { + background: transparent !important; +} + +.section-title { + color: #fff !important; +} + +.page-container h2 { + color: #fff !important; +} + +[class*="page-container"] [style*="background: #fff"], +[class*="page-container"] [style*="background:#fff"] { + background: #2d2d44 !important; +} + +.el-image { + background: rgba(255, 255, 255, 0.04) !important; +} + +.el-image-viewer__mask { + background: rgba(0, 0, 0, 0.8) !important; +} + +.el-overlay { + background-color: rgba(0, 0, 0, 0.6) !important; +} + +.el-drawer { + background: #1e1e2e !important; + border: 1px solid rgba(255, 255, 255, 0.06) !important; +} + +.el-drawer__header { + color: #fff !important; + border-bottom: 1px solid rgba(255, 255, 255, 0.06) !important; +} + +.el-drawer__body { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-dialog__body { + color: rgba(255, 255, 255, 0.85) !important; + padding: 20px 24px !important; +} + +.el-dialog__footer { + border-top: 1px solid rgba(255, 255, 255, 0.06) !important; + padding: 16px 24px !important; +} + +.el-input-number { + width: 100%; +} + +.el-input-number .el-input__wrapper { + background: rgba(255, 255, 255, 0.06) !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 8px !important; + box-shadow: none !important; +} + +.el-input-number .el-input__inner { + color: rgba(255, 255, 255, 0.85) !important; + text-align: left !important; +} + +.el-input-number__decrease, +.el-input-number__increase { + background: transparent !important; + border-color: rgba(255, 255, 255, 0.08) !important; + color: rgba(255, 255, 255, 0.5) !important; +} + +.el-input-number__decrease:hover, +.el-input-number__increase:hover { + color: #4e6ef2 !important; +} + +.el-date-editor { + --el-input-bg-color: rgba(255, 255, 255, 0.06) !important; + --el-input-border-color: rgba(255, 255, 255, 0.08) !important; + --el-input-hover-border-color: rgba(255, 255, 255, 0.15) !important; + --el-input-focus-border-color: #4e6ef2 !important; +} + +.el-date-editor .el-input__wrapper { + background: rgba(255, 255, 255, 0.06) !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 8px !important; + box-shadow: none !important; +} + +.el-date-editor .el-input__inner { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-picker-panel { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; + border-radius: 8px !important; +} + +.el-picker-panel__body { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-date-picker__header-label { + color: rgba(255, 255, 255, 0.6) !important; +} + +.el-date-picker__header-label:hover { + color: #4e6ef2 !important; +} + +.el-date-table th { + color: rgba(255, 255, 255, 0.4) !important; +} + +.el-date-table td.available:hover { + color: #4e6ef2 !important; +} + +.el-date-table td.today span { + color: #4e6ef2 !important; +} + +.el-date-table td.current:not(.disabled) span { + background: #4e6ef2 !important; + color: #fff !important; +} + +.el-month-table td .cell { + color: rgba(255, 255, 255, 0.6) !important; +} + +.el-month-table td .cell:hover { + color: #4e6ef2 !important; +} + +.el-year-table td .cell { + color: rgba(255, 255, 255, 0.6) !important; +} + +.el-year-table td .cell:hover { + color: #4e6ef2 !important; +} + +.el-time-panel { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +.el-time-panel__content { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-time-spinner__item { + color: rgba(255, 255, 255, 0.5) !important; +} + +.el-time-spinner__item:hover, +.el-time-spinner__item.active { + color: #4e6ef2 !important; +} + +.el-form-item { + margin-bottom: 18px !important; +} + +.el-form-item__content { + line-height: 32px !important; +} + +.el-row { + margin-left: -8px !important; + margin-right: -8px !important; +} + +.el-col { + padding-left: 8px !important; + padding-right: 8px !important; +} + +.el-divider__text { + background-color: #2d2d44 !important; + color: rgba(255, 255, 255, 0.5) !important; +} + +.el-descriptions { + --el-descriptions-item-bordered-label-background: rgba(255, 255, 255, 0.03) !important; +} + +.el-descriptions__body { + background: transparent !important; +} + +.el-descriptions__table { + border-color: rgba(255, 255, 255, 0.06) !important; +} + +.el-descriptions__cell { + border-color: rgba(255, 255, 255, 0.06) !important; +} + +.el-dialog__header { + border-bottom: 1px solid rgba(255, 255, 255, 0.06) !important; + padding: 16px 24px !important; + margin-right: 0 !important; +} + +.el-dialog__footer .el-button + .el-button { + margin-left: 12px; +} + +.el-form-item__error { + color: #f87171 !important; +} + +.el-form-item--feedback .el-input__wrapper { + border-color: #f87171 !important; +} + +.el-select .el-tag { + background: rgba(78, 110, 242, 0.15) !important; + color: #4e6ef2 !important; + border: none !important; +} + +.el-select .el-tag .el-tag__close { + color: #4e6ef2 !important; +} + +.el-select .el-tag .el-tag__close:hover { + background: rgba(78, 110, 242, 0.3) !important; + color: #fff !important; +} + +.el-popconfirm__main { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-popconfirm__action { + margin-top: 12px !important; +} + +.el-popover.el-popper { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +.el-popover__title { + color: #fff !important; +} + +.el-popover.el-popper[data-popper-placement^="bottom"] .el-popper__arrow::before, +.el-popover.el-popper[data-popper-placement^="top"] .el-popper__arrow::before, +.el-popover.el-popper[data-popper-placement^="left"] .el-popper__arrow::before, +.el-popover.el-popper[data-popper-placement^="right"] .el-popper__arrow::before { + background: #2d2d44 !important; + border-color: rgba(255, 255, 255, 0.08) !important; +} + +.el-cascader-panel { + background: #2d2d44 !important; +} + +.el-cascader-menu { + border-color: rgba(255, 255, 255, 0.06) !important; +} + +.el-cascader-node { + color: rgba(255, 255, 255, 0.65) !important; +} + +.el-cascader-node:hover, +.el-cascader-node.is-active { + background: rgba(255, 255, 255, 0.06) !important; +} + +.el-cascader-node.in-active-path, +.el-cascader-node.is-active { + color: #4e6ef2 !important; +} + +.el-transfer-panel { + background: rgba(255, 255, 255, 0.04) !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +.el-transfer-panel__header { + background: rgba(255, 255, 255, 0.03) !important; + border-bottom: 1px solid rgba(255, 255, 255, 0.06) !important; + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-transfer-panel__item { + color: rgba(255, 255, 255, 0.65) !important; +} + +.el-transfer-panel__item:hover { + color: #fff !important; +} + +.el-transfer__buttons .el-button { + background: #2d2d44 !important; + border: 1px solid rgba(255, 255, 255, 0.1) !important; + color: rgba(255, 255, 255, 0.8) !important; +} + +.el-transfer__buttons .el-button:hover { + border-color: #4e6ef2 !important; + color: #4e6ef2 !important; +} + +.el-slider__runway { + background: rgba(255, 255, 255, 0.1) !important; +} + +.el-slider__bar { + background: #4e6ef2 !important; +} + +.el-slider__button { + border-color: #4e6ef2 !important; +} + +.el-rate__icon { + color: rgba(255, 255, 255, 0.2) !important; +} + +.el-rate__icon.is-active { + color: #f59e0b !important; +} + +.el-color-picker__trigger { + border-color: rgba(255, 255, 255, 0.1) !important; +} + +.el-color-dropdown__main-wrapper { + background: #2d2d44 !important; +} + +.el-color-dropdown__btns { + border-top: 1px solid rgba(255, 255, 255, 0.06) !important; + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-upload-dragger { + background: rgba(255, 255, 255, 0.04) !important; + border: 1px dashed rgba(255, 255, 255, 0.15) !important; +} + +.el-upload-dragger:hover { + border-color: #4e6ef2 !important; +} + +.el-upload__text { + color: rgba(255, 255, 255, 0.5) !important; +} + +.el-upload__text em { + color: #4e6ef2 !important; +} + +.el-progress__text { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-tree { + background: transparent !important; + color: rgba(255, 255, 255, 0.75) !important; +} + +.el-tree-node__content:hover { + background: rgba(255, 255, 255, 0.04) !important; +} + +.el-tree-node.is-current > .el-tree-node__content { + background: rgba(78, 110, 242, 0.1) !important; + color: #4e6ef2 !important; +} + +.el-calendar { + background: #2d2d44 !important; +} + +.el-calendar__header { + border-bottom: 1px solid rgba(255, 255, 255, 0.06) !important; +} + +.el-calendar-table td { + border-color: rgba(255, 255, 255, 0.06) !important; +} + +.el-calendar-table .el-calendar-day { + color: rgba(255, 255, 255, 0.65) !important; +} + +.el-calendar-table .el-calendar-day:hover { + background: rgba(255, 255, 255, 0.04) !important; +} + +.el-calendar-table td.is-selected { + background: rgba(78, 110, 242, 0.15) !important; +} + +.el-calendar-table td.is-today { + color: #4e6ef2 !important; +} + +.el-backtop { + background: #4e6ef2 !important; + color: #fff !important; +} + +.el-backtop:hover { + background: #5a7af5 !important; +} + +.el-skeleton__item { + background: rgba(255, 255, 255, 0.06) !important; +} + +.el-skeleton.is-animated .el-skeleton__item { + background: linear-gradient(90deg, rgba(255, 255, 255, 0.06) 25%, rgba(255, 255, 255, 0.1) 37%, rgba(255, 255, 255, 0.06) 63%) !important; +} + +.el-result__title { + color: #fff !important; +} + +.el-result__subtitle { + color: rgba(255, 255, 255, 0.55) !important; +} + +.el-timeline-item__timestamp { + color: rgba(255, 255, 255, 0.4) !important; +} + +.el-timeline-item__tail { + border-left-color: rgba(255, 255, 255, 0.1) !important; +} + +.el-timeline-item__node { + background: rgba(255, 255, 255, 0.1) !important; +} + +.el-timeline-item__node--primary { + background: #4e6ef2 !important; +} + +.el-collapse { + border-color: rgba(255, 255, 255, 0.06) !important; +} + +.el-collapse-item__header { + background: transparent !important; + border-color: rgba(255, 255, 255, 0.06) !important; + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-collapse-item__header:hover { + background: rgba(255, 255, 255, 0.04) !important; +} + +.el-collapse-item__wrap { + background: transparent !important; + border-color: rgba(255, 255, 255, 0.06) !important; +} + +.el-collapse-item__content { + color: rgba(255, 255, 255, 0.65) !important; +} + +.el-carousel__button { + background: rgba(255, 255, 255, 0.3) !important; +} + +.el-carousel__indicator.is-active .el-carousel__button { + background: #4e6ef2 !important; +} + +.el-badge__content { + background: #ef4444 !important; + border-color: #2d2d44 !important; + color: #fff !important; +} + +.el-badge__content.is-fixed { + top: 2px !important; + right: calc(-10px + var(--el-badge-size) / 2) !important; +} + +.el-loading-mask { + background-color: rgba(26, 26, 46, 0.8) !important; +} + +.el-loading-spinner .path { + stroke: #4e6ef2 !important; +} + +.el-loading-spinner .el-loading-text { + color: #4e6ef2 !important; +} + +.el-text { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-text.el-text--primary { + color: #4e6ef2 !important; +} + +.el-text.el-text--success { + color: #10b981 !important; +} + +.el-text.el-text--warning { + color: #f59e0b !important; +} + +.el-text.el-text--danger { + color: #ef4444 !important; +} + +.el-text.el-text--info { + color: rgba(255, 255, 255, 0.55) !important; +} + +.el-statistic__head { + color: rgba(255, 255, 255, 0.5) !important; +} + +.el-statistic__content { + color: #fff !important; +} + +.el-segmented { + background: rgba(255, 255, 255, 0.04) !important; +} + +.el-segmented__item { + color: rgba(255, 255, 255, 0.65) !important; +} + +.el-segmented__item:hover { + color: #fff !important; +} + +.el-segmented__item-selected { + background: #4e6ef2 !important; + color: #fff !important; +} + +.el-segmented__item-selected:hover { + color: #fff !important; +} + +.el-upload-list__item-name { + color: rgba(255, 255, 255, 0.85) !important; +} + +.el-upload-list__item:hover { + background: rgba(255, 255, 255, 0.04) !important; +} + +.el-upload-list__item-status-label { + color: rgba(255, 255, 255, 0.5) !important; +} + +.el-upload-list__item.is-success .el-upload-list__item-status-label { + color: #10b981 !important; +} + +.el-upload-list__item.is-error .el-upload-list__item-status-label { + color: #ef4444 !important; +} + +.el-upload-list__item-delete { + color: rgba(255, 255, 255, 0.5) !important; +} + +.el-upload-list__item-delete:hover { + color: #f87171 !important; +} + +.el-upload-dragger { + background: rgba(255, 255, 255, 0.04) !important; + border: 1px dashed rgba(255, 255, 255, 0.15) !important; + border-radius: 8px !important; +} + +.el-upload-dragger:hover { + border-color: #4e6ef2 !important; +} + +.el-upload__text { + color: rgba(255, 255, 255, 0.5) !important; +} + +.el-upload__text em { + color: #4e6ef2 !important; +} + +.el-upload--picture-card { + background: rgba(255, 255, 255, 0.06) !important; + border: 1px dashed rgba(255, 255, 255, 0.15) !important; + border-radius: 8px !important; +} + +.el-upload--picture-card:hover { + border-color: #4e6ef2 !important; +} + +.el-upload-list--picture-card .el-upload-list__item { + background: rgba(255, 255, 255, 0.06) !important; + border: 1px solid rgba(255, 255, 255, 0.08) !important; +} + +.el-upload-list--picture .el-upload-list__item { + background: rgba(255, 255, 255, 0.04) !important; + border: 1px solid rgba(255, 255, 255, 0.06) !important; +} + +.el-upload-list--picture .el-upload-list__item-thumbnail { + background: rgba(255, 255, 255, 0.04) !important; +} diff --git a/frontend/src/assets/typescript.svg b/frontend/src/assets/typescript.svg new file mode 100644 index 0000000..6c9d69c --- /dev/null +++ b/frontend/src/assets/typescript.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/vite.svg b/frontend/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/frontend/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/frontend/src/components/BackNav.vue b/frontend/src/components/BackNav.vue new file mode 100644 index 0000000..0358036 --- /dev/null +++ b/frontend/src/components/BackNav.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts new file mode 100644 index 0000000..9ab5af5 --- /dev/null +++ b/frontend/src/i18n/index.ts @@ -0,0 +1,13 @@ +import { createI18n } from 'vue-i18n' +import zh from './locales/zh.json' +import en from './locales/en.json' +import ja from './locales/ja.json' + +const i18n = createI18n({ + legacy: false, + locale: localStorage.getItem('locale') || 'zh', + fallbackLocale: 'zh', + messages: { zh, en, ja }, +}) + +export default i18n diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json new file mode 100644 index 0000000..20dd1a9 --- /dev/null +++ b/frontend/src/i18n/locales/en.json @@ -0,0 +1,145 @@ +{ + "common": { + "home": "Home", + "products": "Products", + "cart": "Cart", + "orders": "Orders", + "profile": "Profile", + "login": "Login", + "register": "Register", + "logout": "Logout", + "search": "Search", + "save": "Save", + "cancel": "Cancel", + "delete": "Delete", + "edit": "Edit", + "create": "Create", + "submit": "Submit", + "confirm": "Confirm", + "back": "Back", + "loading": "Loading...", + "success": "Success", + "error": "Error", + "noData": "No data", + "total": "Total {count} items", + "language": "Language" + }, + "auth": { + "email": "Email", + "username": "Username", + "password": "Password", + "confirmPassword": "Confirm Password", + "inviteCode": "Invite Code", + "forgotPassword": "Forgot Password", + "resetPassword": "Reset Password", + "loginSuccess": "Login successful", + "registerSuccess": "Registration successful", + "emailVerified": "Email verified", + "sendVerifyCode": "Send Code", + "verifyEmail": "Verify Email" + }, + "product": { + "name": "Product Name", + "price": "Price", + "description": "Description", + "category": "Category", + "stock": "Stock", + "addToCart": "Add to Cart", + "buyNow": "Buy Now", + "requireCredit": "Requires Purchase Credit", + "creditCost": "Credit Cost", + "creditReward": "Credit Reward", + "minPurchase": "Min Purchase", + "maxPurchase": "Max Purchase", + "customFields": "Custom Fields", + "filterByCategory": "Filter by Category", + "searchProducts": "Search Products" + }, + "cart": { + "empty": "Cart is empty", + "total": "Total", + "checkout": "Checkout", + "quantity": "Quantity", + "subtotal": "Subtotal", + "removeItem": "Remove Item" + }, + "order": { + "orderNumber": "Order No.", + "status": "Status", + "totalAmount": "Total Amount", + "createTime": "Created At", + "pendingPayment": "Pending Payment", + "pendingConfirm": "Pending Confirm", + "pendingShip": "Pending Shipment", + "shipped": "Shipped", + "completed": "Completed", + "refunding": "Refunding", + "refunded": "Refunded", + "cancelled": "Cancelled", + "applyRefund": "Apply Refund", + "refundReason": "Refund Reason", + "trackingNumber": "Tracking Number", + "shippingAddress": "Shipping Address", + "exportOrders": "Export Orders" + }, + "address": { + "name": "Recipient", + "phone": "Phone", + "province": "Province", + "city": "City", + "district": "District", + "detail": "Address", + "setDefault": "Set Default", + "addAddress": "Add Address", + "editAddress": "Edit Address" + }, + "lottery": { + "title": "Lottery", + "register": "Register", + "registered": "Registered", + "drawTime": "Draw Time", + "prizes": "Prizes", + "winners": "Winners" + }, + "ticket": { + "title": "Tickets", + "createTicket": "Create Ticket", + "subject": "Subject", + "content": "Content", + "category": "Category", + "status": "Status", + "pending": "Pending", + "processing": "Processing", + "resolved": "Resolved", + "closed": "Closed", + "reply": "Reply" + }, + "admin": { + "dashboard": "Dashboard", + "userManagement": "User Management", + "productManagement": "Product Management", + "categoryManagement": "Category Management", + "orderManagement": "Order Management", + "supplierManagement": "Supplier Management", + "lotteryManagement": "Lottery Management", + "ticketManagement": "Ticket Management", + "systemSettings": "System Settings", + "menuManagement": "Menu Management", + "smtpSettings": "SMTP Settings", + "paymentSettings": "Payment Settings", + "shippingSettings": "Shipping Settings", + "inviteSettings": "Invite Settings", + "channelFeeRate": "Channel Fee Rate" + }, + "supplier": { + "dashboard": "Supplier Dashboard", + "orderManagement": "Order Management", + "inventoryManagement": "Inventory Management", + "confirmOrder": "Confirm Order", + "shipOrder": "Ship Order", + "shippingPhoto": "Shipping Photo", + "expressPhoto": "Express Photo", + "customsPhoto": "Customs Photo", + "updateStock": "Update Stock" + } +} diff --git a/frontend/src/i18n/locales/ja.json b/frontend/src/i18n/locales/ja.json new file mode 100644 index 0000000..87edea5 --- /dev/null +++ b/frontend/src/i18n/locales/ja.json @@ -0,0 +1,145 @@ +{ + "common": { + "home": "ホーム", + "products": "商品", + "cart": "カート", + "orders": "注文", + "profile": "マイページ", + "login": "ログイン", + "register": "新規登録", + "logout": "ログアウト", + "search": "検索", + "save": "保存", + "cancel": "キャンセル", + "delete": "削除", + "edit": "編集", + "create": "作成", + "submit": "送信", + "confirm": "確認", + "back": "戻る", + "loading": "読み込み中...", + "success": "成功しました", + "error": "エラーが発生しました", + "noData": "データがありません", + "total": "{count}件", + "language": "言語" + }, + "auth": { + "email": "メールアドレス", + "username": "ユーザー名", + "password": "パスワード", + "confirmPassword": "パスワード確認", + "inviteCode": "招待コード", + "forgotPassword": "パスワードをお忘れですか", + "resetPassword": "パスワードリセット", + "loginSuccess": "ログイン成功", + "registerSuccess": "登録成功", + "emailVerified": "メール認証済み", + "sendVerifyCode": "認証コード送信", + "verifyEmail": "メール認証" + }, + "product": { + "name": "商品名", + "price": "価格", + "description": "説明", + "category": "カテゴリ", + "stock": "在庫", + "addToCart": "カートに追加", + "buyNow": "今すぐ購入", + "requireCredit": "購入資格が必要", + "creditCost": "必要資格数", + "creditReward": "付与資格数", + "minPurchase": "最小購入数", + "maxPurchase": "最大購入数", + "customFields": "カスタムフィールド", + "filterByCategory": "カテゴリで絞り込み", + "searchProducts": "商品を検索" + }, + "cart": { + "empty": "カートは空です", + "total": "合計", + "checkout": "注文する", + "quantity": "数量", + "subtotal": "小計", + "removeItem": "削除" + }, + "order": { + "orderNumber": "注文番号", + "status": "ステータス", + "totalAmount": "合計金額", + "createTime": "注文日時", + "pendingPayment": "支払待ち", + "pendingConfirm": "確認待ち", + "pendingShip": "発送待ち", + "shipped": "発送済み", + "completed": "完了", + "refunding": "返金中", + "refunded": "返金済み", + "cancelled": "キャンセル", + "applyRefund": "返金申請", + "refundReason": "返金理由", + "trackingNumber": "追跡番号", + "shippingAddress": "配送先", + "exportOrders": "注文エクスポート" + }, + "address": { + "name": "受取人", + "phone": "電話番号", + "province": "都道府県", + "city": "市区町村", + "district": "区", + "detail": "住所", + "setDefault": "デフォルトに設定", + "addAddress": "住所を追加", + "editAddress": "住所を編集" + }, + "lottery": { + "title": "抽選", + "register": "参加登録", + "registered": "登録済み", + "drawTime": "抽選日時", + "prizes": "賞品", + "winners": "当選者" + }, + "ticket": { + "title": "チケット", + "createTicket": "チケット作成", + "subject": "件名", + "content": "内容", + "category": "カテゴリ", + "status": "ステータス", + "pending": "未対応", + "processing": "対応中", + "resolved": "解決済み", + "closed": "クローズ", + "reply": "返信" + }, + "admin": { + "dashboard": "ダッシュボード", + "userManagement": "ユーザー管理", + "productManagement": "商品管理", + "categoryManagement": "カテゴリ管理", + "orderManagement": "注文管理", + "supplierManagement": "サプライヤー管理", + "lotteryManagement": "抽選管理", + "ticketManagement": "チケット管理", + "systemSettings": "システム設定", + "menuManagement": "メニュー管理", + "smtpSettings": "SMTP設定", + "paymentSettings": "決済設定", + "shippingSettings": "配送設定", + "inviteSettings": "招待設定", + "channelFeeRate": "チャネル手数料率" + }, + "supplier": { + "dashboard": "サプライヤーダッシュボード", + "orderManagement": "注文管理", + "inventoryManagement": "在庫管理", + "confirmOrder": "注文確認", + "shipOrder": "発送確認", + "shippingPhoto": "発送写真", + "expressPhoto": "配送伝票写真", + "customsPhoto": "税関申告書", + "updateStock": "在庫更新" + } +} diff --git a/frontend/src/i18n/locales/zh.json b/frontend/src/i18n/locales/zh.json new file mode 100644 index 0000000..683a219 --- /dev/null +++ b/frontend/src/i18n/locales/zh.json @@ -0,0 +1,145 @@ +{ + "common": { + "home": "首页", + "products": "商品", + "cart": "购物车", + "orders": "订单", + "profile": "个人中心", + "login": "登录", + "register": "注册", + "logout": "退出登录", + "search": "搜索", + "save": "保存", + "cancel": "取消", + "delete": "删除", + "edit": "编辑", + "create": "创建", + "submit": "提交", + "confirm": "确认", + "back": "返回", + "loading": "加载中...", + "success": "操作成功", + "error": "操作失败", + "noData": "暂无数据", + "total": "共 {count} 条", + "language": "语言" + }, + "auth": { + "email": "邮箱", + "username": "用户名", + "password": "密码", + "confirmPassword": "确认密码", + "inviteCode": "邀请码", + "forgotPassword": "忘记密码", + "resetPassword": "重置密码", + "loginSuccess": "登录成功", + "registerSuccess": "注册成功", + "emailVerified": "邮箱已验证", + "sendVerifyCode": "发送验证码", + "verifyEmail": "验证邮箱" + }, + "product": { + "name": "商品名称", + "price": "价格", + "description": "描述", + "category": "分类", + "stock": "库存", + "addToCart": "加入购物车", + "buyNow": "立即购买", + "requireCredit": "需要购买资格", + "creditCost": "需要资格数", + "creditReward": "赠送资格数", + "minPurchase": "最低购买数量", + "maxPurchase": "最大购买数量", + "customFields": "自定义字段", + "filterByCategory": "按分类筛选", + "searchProducts": "搜索商品" + }, + "cart": { + "empty": "购物车为空", + "total": "合计", + "checkout": "结算", + "quantity": "数量", + "subtotal": "小计", + "removeItem": "移除商品" + }, + "order": { + "orderNumber": "订单号", + "status": "状态", + "totalAmount": "总金额", + "createTime": "创建时间", + "pendingPayment": "待支付", + "pendingConfirm": "待确认", + "pendingShip": "待发货", + "shipped": "已发货", + "completed": "已完成", + "refunding": "退款中", + "refunded": "已退款", + "cancelled": "已取消", + "applyRefund": "申请退款", + "refundReason": "退款原因", + "trackingNumber": "快递单号", + "shippingAddress": "收货地址", + "exportOrders": "导出订单" + }, + "address": { + "name": "收件人", + "phone": "联系电话", + "province": "省份", + "city": "城市", + "district": "区县", + "detail": "详细地址", + "setDefault": "设为默认", + "addAddress": "添加地址", + "editAddress": "编辑地址" + }, + "lottery": { + "title": "抽奖活动", + "register": "报名", + "registered": "已报名", + "drawTime": "抽奖时间", + "prizes": "奖品", + "winners": "中奖名单" + }, + "ticket": { + "title": "工单", + "createTicket": "提交工单", + "subject": "标题", + "content": "内容", + "category": "分类", + "status": "状态", + "pending": "待处理", + "processing": "处理中", + "resolved": "已解决", + "closed": "已关闭", + "reply": "回复" + }, + "admin": { + "dashboard": "仪表盘", + "userManagement": "用户管理", + "productManagement": "商品管理", + "categoryManagement": "分类管理", + "orderManagement": "订单管理", + "supplierManagement": "供货商管理", + "lotteryManagement": "抽奖管理", + "ticketManagement": "工单管理", + "systemSettings": "系统设置", + "menuManagement": "菜单管理", + "smtpSettings": "SMTP设置", + "paymentSettings": "支付通道设置", + "shippingSettings": "运费设置", + "inviteSettings": "邀请奖励设置", + "channelFeeRate": "通道费率" + }, + "supplier": { + "dashboard": "供货商仪表盘", + "orderManagement": "订单管理", + "inventoryManagement": "库存管理", + "confirmOrder": "确认订单", + "shipOrder": "确认发货", + "shippingPhoto": "发货照片", + "expressPhoto": "快递单照片", + "customsPhoto": "报关清单", + "updateStock": "更新库存" + } +} diff --git a/frontend/src/layouts/AdminLayout.vue b/frontend/src/layouts/AdminLayout.vue new file mode 100644 index 0000000..a11509e --- /dev/null +++ b/frontend/src/layouts/AdminLayout.vue @@ -0,0 +1,383 @@ + + + + + diff --git a/frontend/src/layouts/SupplierLayout.vue b/frontend/src/layouts/SupplierLayout.vue new file mode 100644 index 0000000..318cb3c --- /dev/null +++ b/frontend/src/layouts/SupplierLayout.vue @@ -0,0 +1,329 @@ + + + + + diff --git a/frontend/src/layouts/scheme4/UserLayout.vue b/frontend/src/layouts/scheme4/UserLayout.vue new file mode 100644 index 0000000..b7b294e --- /dev/null +++ b/frontend/src/layouts/scheme4/UserLayout.vue @@ -0,0 +1,479 @@ + + + + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..d9b9409 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,16 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import 'element-plus/theme-chalk/dark/css-vars.css' +import App from './App.vue' +import router from './router' +import i18n from './i18n' +import './assets/styles/global.scss' + +const app = createApp(App) +app.use(createPinia()) +app.use(router) +app.use(i18n) +app.use(ElementPlus) +app.mount('#app') diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 0000000..f2d027a --- /dev/null +++ b/frontend/src/router/index.ts @@ -0,0 +1,111 @@ +import { createRouter, createWebHistory } from 'vue-router' +import { systemApi } from '../api' + +const routes = [ + { + path: '/install', + name: 'Install', + component: () => import('../views/Install.vue'), + meta: { install: true }, + }, + { + path: '/', + component: () => import('../layouts/scheme4/UserLayout.vue'), + children: [ + { path: '', name: 'Home', component: () => import('../views/scheme4/Home.vue') }, + { path: 'products', name: 'Products', component: () => import('../views/user/Products.vue') }, + { path: 'products/:id', name: 'ProductDetail', component: () => import('../views/user/ProductDetail.vue') }, + { path: 'cart', name: 'Cart', component: () => import('../views/user/Cart.vue') }, + { path: 'orders', name: 'Orders', component: () => import('../views/user/Orders.vue') }, + { path: 'orders/:id', name: 'OrderDetail', component: () => import('../views/user/OrderDetail.vue') }, + { path: 'profile', name: 'Profile', component: () => import('../views/user/Profile.vue') }, + { path: 'addresses', name: 'Addresses', component: () => import('../views/user/Addresses.vue') }, + { path: 'lotteries', name: 'Lotteries', component: () => import('../views/user/Lotteries.vue') }, + { path: 'lotteries/:id', name: 'LotteryDetail', component: () => import('../views/user/LotteryDetail.vue') }, + { path: 'tickets', name: 'Tickets', component: () => import('../views/user/Tickets.vue') }, + { path: 'tickets/create', name: 'CreateTicket', component: () => import('../views/user/CreateTicket.vue') }, + { path: 'articles', name: 'Articles', component: () => import('../views/user/Articles.vue') }, + { path: 'articles/:id', name: 'ArticleDetail', component: () => import('../views/user/ArticleDetail.vue') }, + { path: 'login', name: 'Login', component: () => import('../views/auth/Login.vue'), meta: { guest: true, hideSidebar: true } }, + { path: 'register', name: 'Register', component: () => import('../views/auth/Register.vue'), meta: { guest: true, hideSidebar: true } }, + { path: 'forgot-password', name: 'ForgotPassword', component: () => import('../views/auth/ForgotPassword.vue'), meta: { guest: true, hideSidebar: true } }, + ], + }, + { + path: '/admin', + component: () => import('../layouts/AdminLayout.vue'), + meta: { requiresAuth: true, role: 'admin' }, + children: [ + { path: '', name: 'AdminDashboard', component: () => import('../views/admin/Dashboard.vue') }, + { path: 'users', name: 'AdminUsers', component: () => import('../views/admin/Users.vue') }, + { path: 'categories', name: 'AdminCategories', component: () => import('../views/admin/Categories.vue') }, + { path: 'products', name: 'AdminProducts', component: () => import('../views/admin/Products.vue') }, + { path: 'orders', name: 'AdminOrders', component: () => import('../views/admin/Orders.vue') }, + { path: 'suppliers', name: 'AdminSuppliers', component: () => import('../views/admin/Suppliers.vue') }, + { path: 'lotteries', name: 'AdminLotteries', component: () => import('../views/admin/Lotteries.vue') }, + { path: 'tickets', name: 'AdminTickets', component: () => import('../views/admin/Tickets.vue') }, + { path: 'settings', name: 'AdminSettings', component: () => import('../views/admin/Settings.vue') }, + { path: 'articles', name: 'AdminArticles', component: () => import('../views/admin/Articles.vue') }, + ], + }, + { + path: '/supplier', + component: () => import('../layouts/SupplierLayout.vue'), + meta: { requiresAuth: true, role: 'supplier' }, + children: [ + { path: '', name: 'SupplierDashboard', component: () => import('../views/supplier/Dashboard.vue') }, + { path: 'orders', name: 'SupplierOrders', component: () => import('../views/supplier/Orders.vue') }, + { path: 'inventory', name: 'SupplierInventory', component: () => import('../views/supplier/Inventory.vue') }, + ], + }, +] + +const router = createRouter({ + history: createWebHistory(), + routes, +}) + +let installedChecked = false +let isInstalled = false + +async function checkInstalled() { + if (installedChecked) return isInstalled + try { + const res: any = await systemApi.checkInstalled() + isInstalled = res.installed + installedChecked = true + return isInstalled + } catch { + return true + } +} + +router.beforeEach(async (to, _from, next) => { + const installed = await checkInstalled() + + if (!installed && to.path !== '/install') { + next('/install') + return + } + + if (installed && to.path === '/install') { + next('/') + return + } + + const token = localStorage.getItem('token') + const userStr = localStorage.getItem('user') + const user = userStr && userStr !== 'undefined' ? JSON.parse(userStr) : null + + if (to.meta.requiresAuth && !token) { + next('/login') + } else if (to.meta.role && user?.role !== to.meta.role && user?.role !== 'admin') { + next('/') + } else if (to.meta.guest && token) { + next('/') + } else { + next() + } +}) + +export default router diff --git a/frontend/src/store/cart.ts b/frontend/src/store/cart.ts new file mode 100644 index 0000000..96941a9 --- /dev/null +++ b/frontend/src/store/cart.ts @@ -0,0 +1,62 @@ +import { ref, watch } from 'vue' +import { defineStore } from 'pinia' +import { cartApi } from '../api' + +export const useCartStore = defineStore('cart', () => { + const items = ref([]) + const loading = ref(false) + const totalAmount = ref(0) + const totalCount = ref(0) + + async function fetchCart() { + loading.value = true + try { + const res: any = await cartApi.list() + items.value = res.data || [] + calcTotals() + } finally { + loading.value = false + } + } + + async function addItem(productId: number, quantity: number) { + await cartApi.add({ product_id: productId, quantity }) + await fetchCart() + } + + async function updateItem(id: number, quantity: number) { + await cartApi.update(id, { quantity }) + await fetchCart() + } + + async function removeItem(id: number) { + await cartApi.delete(id) + await fetchCart() + } + + async function removeItems(ids: number[]) { + for (const id of ids) { + await cartApi.delete(id) + } + await fetchCart() + } + + function calcTotals() { + totalAmount.value = items.value.reduce((sum, item) => sum + (item.product?.price || 0) * item.quantity, 0) + totalCount.value = items.value.reduce((sum, item) => sum + item.quantity, 0) + } + + watch(items, calcTotals, { deep: true }) + + return { + items, + loading, + totalAmount, + totalCount, + fetchCart, + addItem, + updateItem, + removeItem, + removeItems + } +}) diff --git a/frontend/src/store/user.ts b/frontend/src/store/user.ts new file mode 100644 index 0000000..3e75831 --- /dev/null +++ b/frontend/src/store/user.ts @@ -0,0 +1,53 @@ +import { defineStore } from 'pinia' +import { ref, computed } from 'vue' +import { authApi, userApi } from '../api' + +export const useUserStore = defineStore('user', () => { + const token = ref(localStorage.getItem('token') || '') + const userStr = localStorage.getItem('user') + const user = ref(userStr && userStr !== 'undefined' ? JSON.parse(userStr) : null) + + const isLoggedIn = computed(() => !!token.value) + const isAdmin = computed(() => user.value?.role === 'admin') + const isSupplier = computed(() => user.value?.role === 'supplier') + + async function login(data: { email: string; password: string }) { + const res: any = await authApi.login(data) + token.value = res.token + user.value = res.user + localStorage.setItem('token', res.token) + localStorage.setItem('user', JSON.stringify(res.user)) + return res + } + + async function register(data: any) { + const res: any = await authApi.register(data) + token.value = res.token + user.value = res.user + localStorage.setItem('token', res.token) + localStorage.setItem('user', JSON.stringify(res.user)) + return res + } + + async function fetchProfile() { + const res: any = await userApi.getProfile() + user.value = res.data || res + localStorage.setItem('user', JSON.stringify(res.data || res)) + } + + function logout() { + token.value = '' + user.value = null + localStorage.removeItem('token') + localStorage.removeItem('user') + } + + function setUser(userData: any, userToken: string) { + token.value = userToken + user.value = userData + localStorage.setItem('token', userToken) + localStorage.setItem('user', JSON.stringify(userData)) + } + + return { token, user, isLoggedIn, isAdmin, isSupplier, login, register, fetchProfile, logout, setUser } +}) diff --git a/frontend/src/utils/image.ts b/frontend/src/utils/image.ts new file mode 100644 index 0000000..c61110f --- /dev/null +++ b/frontend/src/utils/image.ts @@ -0,0 +1,20 @@ +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api' +const BASE_URL = API_BASE_URL.replace('/api', '') + +export function getImageUrl(path: string): string { + if (!path) return '' + if (path.startsWith('http://') || path.startsWith('https://')) { + return path + } + if (path.startsWith('/')) { + return BASE_URL + path + } + return BASE_URL + '/' + path +} + +export function getFirstImage(images: string): string { + if (!images) return '' + const arr = images.split(',').map((s: string) => s.trim()).filter(Boolean) + const first = arr[0] || '' + return getImageUrl(first) +} diff --git a/frontend/src/utils/request.ts b/frontend/src/utils/request.ts new file mode 100644 index 0000000..adf5e7e --- /dev/null +++ b/frontend/src/utils/request.ts @@ -0,0 +1,41 @@ +import axios from 'axios' +import { ElMessage } from 'element-plus' + +const api = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080/api', + timeout: 30000, + headers: { + 'Content-Type': 'application/json', + }, +}) + +api.interceptors.request.use( + (config) => { + const token = localStorage.getItem('token') + if (token) { + config.headers.Authorization = `Bearer ${token}` + } + return config + }, + (error) => Promise.reject(error), +) + +api.interceptors.response.use( + (response) => response.data, + (error) => { + const message = error.response?.data?.error || error.message || 'Request failed' + if (error.response?.status === 401) { + const hadToken = !!localStorage.getItem('token') + localStorage.removeItem('token') + localStorage.removeItem('user') + if (hadToken) { + window.location.href = '/login' + } + } else { + ElMessage.error(message) + } + return Promise.reject(error) + }, +) + +export default api diff --git a/frontend/src/views/Install.vue b/frontend/src/views/Install.vue new file mode 100644 index 0000000..b9462e3 --- /dev/null +++ b/frontend/src/views/Install.vue @@ -0,0 +1,155 @@ + + + + + diff --git a/frontend/src/views/admin/Articles.vue b/frontend/src/views/admin/Articles.vue new file mode 100644 index 0000000..f6c10a5 --- /dev/null +++ b/frontend/src/views/admin/Articles.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/frontend/src/views/admin/Categories.vue b/frontend/src/views/admin/Categories.vue new file mode 100644 index 0000000..48328a9 --- /dev/null +++ b/frontend/src/views/admin/Categories.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/frontend/src/views/admin/Dashboard.vue b/frontend/src/views/admin/Dashboard.vue new file mode 100644 index 0000000..a730bb2 --- /dev/null +++ b/frontend/src/views/admin/Dashboard.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/frontend/src/views/admin/Lotteries.vue b/frontend/src/views/admin/Lotteries.vue new file mode 100644 index 0000000..90a89e8 --- /dev/null +++ b/frontend/src/views/admin/Lotteries.vue @@ -0,0 +1,316 @@ + + + + + diff --git a/frontend/src/views/admin/Orders.vue b/frontend/src/views/admin/Orders.vue new file mode 100644 index 0000000..88cf667 --- /dev/null +++ b/frontend/src/views/admin/Orders.vue @@ -0,0 +1,140 @@ + + + + + diff --git a/frontend/src/views/admin/Products.vue b/frontend/src/views/admin/Products.vue new file mode 100644 index 0000000..6a1afaa --- /dev/null +++ b/frontend/src/views/admin/Products.vue @@ -0,0 +1,516 @@ + + + + + diff --git a/frontend/src/views/admin/Settings.vue b/frontend/src/views/admin/Settings.vue new file mode 100644 index 0000000..4835d61 --- /dev/null +++ b/frontend/src/views/admin/Settings.vue @@ -0,0 +1,126 @@ + + + + + diff --git a/frontend/src/views/admin/Suppliers.vue b/frontend/src/views/admin/Suppliers.vue new file mode 100644 index 0000000..8558e7e --- /dev/null +++ b/frontend/src/views/admin/Suppliers.vue @@ -0,0 +1,159 @@ + + + + + diff --git a/frontend/src/views/admin/Tickets.vue b/frontend/src/views/admin/Tickets.vue new file mode 100644 index 0000000..1e56fb6 --- /dev/null +++ b/frontend/src/views/admin/Tickets.vue @@ -0,0 +1,135 @@ + + + + + diff --git a/frontend/src/views/admin/Users.vue b/frontend/src/views/admin/Users.vue new file mode 100644 index 0000000..49a19e2 --- /dev/null +++ b/frontend/src/views/admin/Users.vue @@ -0,0 +1,123 @@ + + + + + diff --git a/frontend/src/views/auth/ForgotPassword.vue b/frontend/src/views/auth/ForgotPassword.vue new file mode 100644 index 0000000..2700a7d --- /dev/null +++ b/frontend/src/views/auth/ForgotPassword.vue @@ -0,0 +1,119 @@ + + + + + diff --git a/frontend/src/views/auth/Login.vue b/frontend/src/views/auth/Login.vue new file mode 100644 index 0000000..ba50626 --- /dev/null +++ b/frontend/src/views/auth/Login.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/frontend/src/views/auth/Register.vue b/frontend/src/views/auth/Register.vue new file mode 100644 index 0000000..5a085f1 --- /dev/null +++ b/frontend/src/views/auth/Register.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/frontend/src/views/scheme4/Home.vue b/frontend/src/views/scheme4/Home.vue new file mode 100644 index 0000000..b666184 --- /dev/null +++ b/frontend/src/views/scheme4/Home.vue @@ -0,0 +1,517 @@ + + + + + diff --git a/frontend/src/views/supplier/Dashboard.vue b/frontend/src/views/supplier/Dashboard.vue new file mode 100644 index 0000000..407fca7 --- /dev/null +++ b/frontend/src/views/supplier/Dashboard.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/frontend/src/views/supplier/Inventory.vue b/frontend/src/views/supplier/Inventory.vue new file mode 100644 index 0000000..e03b5a8 --- /dev/null +++ b/frontend/src/views/supplier/Inventory.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/frontend/src/views/supplier/Orders.vue b/frontend/src/views/supplier/Orders.vue new file mode 100644 index 0000000..37349ac --- /dev/null +++ b/frontend/src/views/supplier/Orders.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/frontend/src/views/user/Addresses.vue b/frontend/src/views/user/Addresses.vue new file mode 100644 index 0000000..dc58fee --- /dev/null +++ b/frontend/src/views/user/Addresses.vue @@ -0,0 +1,83 @@ + + + + + diff --git a/frontend/src/views/user/ArticleDetail.vue b/frontend/src/views/user/ArticleDetail.vue new file mode 100644 index 0000000..8a29956 --- /dev/null +++ b/frontend/src/views/user/ArticleDetail.vue @@ -0,0 +1,48 @@ + + + + + diff --git a/frontend/src/views/user/Articles.vue b/frontend/src/views/user/Articles.vue new file mode 100644 index 0000000..779d110 --- /dev/null +++ b/frontend/src/views/user/Articles.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/frontend/src/views/user/Cart.vue b/frontend/src/views/user/Cart.vue new file mode 100644 index 0000000..c13c443 --- /dev/null +++ b/frontend/src/views/user/Cart.vue @@ -0,0 +1,959 @@ + + + + + diff --git a/frontend/src/views/user/CreateTicket.vue b/frontend/src/views/user/CreateTicket.vue new file mode 100644 index 0000000..fc6bdca --- /dev/null +++ b/frontend/src/views/user/CreateTicket.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/frontend/src/views/user/Home.vue b/frontend/src/views/user/Home.vue new file mode 100644 index 0000000..da1109a --- /dev/null +++ b/frontend/src/views/user/Home.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/frontend/src/views/user/Lotteries.vue b/frontend/src/views/user/Lotteries.vue new file mode 100644 index 0000000..13b4a6d --- /dev/null +++ b/frontend/src/views/user/Lotteries.vue @@ -0,0 +1,97 @@ + + + + + diff --git a/frontend/src/views/user/LotteryDetail.vue b/frontend/src/views/user/LotteryDetail.vue new file mode 100644 index 0000000..e982ea8 --- /dev/null +++ b/frontend/src/views/user/LotteryDetail.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/frontend/src/views/user/OrderDetail.vue b/frontend/src/views/user/OrderDetail.vue new file mode 100644 index 0000000..ddc8227 --- /dev/null +++ b/frontend/src/views/user/OrderDetail.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/frontend/src/views/user/Orders.vue b/frontend/src/views/user/Orders.vue new file mode 100644 index 0000000..3f6da47 --- /dev/null +++ b/frontend/src/views/user/Orders.vue @@ -0,0 +1,66 @@ + + + + + diff --git a/frontend/src/views/user/ProductDetail.vue b/frontend/src/views/user/ProductDetail.vue new file mode 100644 index 0000000..60ff481 --- /dev/null +++ b/frontend/src/views/user/ProductDetail.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/frontend/src/views/user/Products.vue b/frontend/src/views/user/Products.vue new file mode 100644 index 0000000..1adc243 --- /dev/null +++ b/frontend/src/views/user/Products.vue @@ -0,0 +1,286 @@ + + + + + diff --git a/frontend/src/views/user/Profile.vue b/frontend/src/views/user/Profile.vue new file mode 100644 index 0000000..79deb9b --- /dev/null +++ b/frontend/src/views/user/Profile.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/frontend/src/views/user/Tickets.vue b/frontend/src/views/user/Tickets.vue new file mode 100644 index 0000000..c1c7c8c --- /dev/null +++ b/frontend/src/views/user/Tickets.vue @@ -0,0 +1,67 @@ + + + + + diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..bc7a940 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "es2023", + "module": "esnext", + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..dfefbff --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + server: { + port: 3004, + proxy: { + '/api': { + target: 'http://localhost:8080', + changeOrigin: true, + }, + }, + }, +}) diff --git a/ribenyan_products.json b/ribenyan_products.json new file mode 100644 index 0000000..8379b79 --- /dev/null +++ b/ribenyan_products.json @@ -0,0 +1,7312 @@ +[ + { + "external_id": "1600", + "image_url": "https://ribenyan.com/data/images/goods/98_68f0999bb7849.webp", + "name_cn": "美国精神/AmericanSpirit 有机烟叶 绿松石 12mg 14支装", + "name_jp": "ナチュラルアメリカンスピリットオーガニックリーフターコイズ14本入", + "description": "", + "price_jpy": 420, + "category": "AmericanSpirit/美国精神", + "price_cny": 20.16 + }, + { + "external_id": "1601", + "image_url": "https://ribenyan.com/data/images/goods/99_68f0999bc8b02.webp", + "name_cn": "美国精神/AmericanSpirit 有机烟叶 金色 6mg 14支装", + "name_jp": "ナチュラルアメリカンスピリットオーガニックリーフゴールド14本入", + "description": "", + "price_jpy": 420, + "category": "AmericanSpirit/美国精神", + "price_cny": 20.16 + }, + { + "external_id": "1614", + "image_url": "https://ribenyan.com/data/images/goods/101_68f0999bea15f.webp", + "name_cn": "美国精神/AmericanSpirit 淡味 8mg 14支装", + "name_jp": "ナチュラルアメリカンスピリットライト14本入", + "description": "", + "price_jpy": 420, + "category": "AmericanSpirit/美国精神", + "price_cny": 20.16 + }, + { + "external_id": "1523", + "image_url": "https://ribenyan.com/data/images/goods/102_68f098f36c24c.webp", + "name_cn": "美国精神/AmericanSpirit 有机薄荷 淡味 9mg 14支装", + "name_jp": "ナチュラルアメリカンスピリットオーガニックミントライト14本入", + "description": "", + "price_jpy": 420, + "category": "AmericanSpirit/美国精神", + "price_cny": 20.16 + }, + { + "external_id": "1525", + "image_url": "https://ribenyan.com/data/images/goods/104_68f098f3ad7d1.webp", + "name_cn": "美国精神/AmericanSpirit 有机薄荷 ONE 1mg 14支装", + "name_jp": "ナチュラルアメリカンスピリットオーガニックミントONE14本入", + "description": "", + "price_jpy": 420, + "category": "AmericanSpirit/美国精神", + "price_cny": 20.16 + }, + { + "external_id": "1610", + "image_url": "https://ribenyan.com/data/images/goods/100_68f0999bd9011.webp", + "name_cn": "美国精神/AmericanSpirit 有机烟叶 ONE 1mg 14支装", + "name_jp": "ナチュラルアメリカンスピリットオーガニックリーフONE14本入", + "description": "", + "price_jpy": 420, + "category": "AmericanSpirit/美国精神", + "price_cny": 20.16 + }, + { + "external_id": "1524", + "image_url": "https://ribenyan.com/data/images/goods/103_68f098f3981a3.webp", + "name_cn": "美国精神/AmericanSpirit 有机薄荷 超淡 5mg 14支装", + "name_jp": "ナチュラルアメリカンスピリットオーガニックミントウルトラライト14", + "description": "", + "price_jpy": 420, + "category": "AmericanSpirit/美国精神", + "price_cny": 20.16 + }, + { + "external_id": "1642", + "image_url": "https://ribenyan.com/data/images/goods/75_68f099bbe5c35.webp", + "name_cn": "骆驼/Camel Craft 14mg 硬盒", + "name_jp": "キャメル・クラフト・14・ボックス", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1112", + "image_url": "https://ribenyan.com/data/images/goods/76_68f099bc01c7c.webp", + "name_cn": "骆驼/Camel Craft 12mg 硬盒", + "name_jp": "キャメル・クラフト・12・ボックス", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1528", + "image_url": "https://ribenyan.com/data/images/goods/88_68f098ef775fb.webp", + "name_cn": "骆驼/Camel Craft 薄荷 梨爆珠 5mg 硬盒", + "name_jp": "キャメル・クラフト・M・ペアーカプセル5", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1457", + "image_url": "https://ribenyan.com/data/images/goods/92_68f0999b4b8a2.webp", + "name_cn": "骆驼/Camel Craft 薄荷 莓果爆珠 5mg 长支 细支", + "name_jp": "キャメル・クラフトメンソールベリーカプセル5・100’sスリム", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1527", + "image_url": "https://ribenyan.com/data/images/goods/87_68f098ef6825b.webp", + "name_cn": "骆驼/Camel Craft 薄荷 梨爆珠 8mg 硬盒", + "name_jp": "キャメルクラフト・M・ペアーカプセル・8", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1116", + "image_url": "https://ribenyan.com/data/images/goods/79_68f099bc30e6d.webp", + "name_cn": "骆驼/Camel Craft 薄荷 莓果爆珠 8mg 硬盒", + "name_jp": "キャメル・クラフト・メンソール・ベリーカプセル・8・ボックス", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1643", + "image_url": "https://ribenyan.com/data/images/goods/81_68f099bc51925.webp", + "name_cn": "骆驼/Camel Craft 10mg 硬盒", + "name_jp": "キャメル・クラフト・10・ボックス", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1458", + "image_url": "https://ribenyan.com/data/images/goods/93_68f0999b60edd.webp", + "name_cn": "骆驼/Camel Craft 薄荷 莓果爆珠 1mg 长支 细支", + "name_jp": "キャメル・クラフトメンソールベリーカプセル1・100’sスリム", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1279", + "image_url": "https://ribenyan.com/data/images/goods/89_68f098ef86b79.webp", + "name_cn": "骆驼/Camel Craft 1mg 长支 细支", + "name_jp": "キャメル・クラフト・1・100’s・スリム", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1113", + "image_url": "https://ribenyan.com/data/images/goods/82_68f098eeb3844.webp", + "name_cn": "骆驼/Camel Craft 6mg 硬盒", + "name_jp": "キャメル・クラフト・6・ボックス", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1117", + "image_url": "https://ribenyan.com/data/images/goods/80_68f099bc410a9.webp", + "name_cn": "骆驼/Camel Craft 薄荷 莓果爆珠 5mg 硬盒", + "name_jp": "キャメル・クラフト・メンソール・ベリーカプセル・5・ボックス", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1644", + "image_url": "https://ribenyan.com/data/images/goods/83_68f098eec480d.webp", + "name_cn": "骆驼/Camel Craft 3mg 硬盒", + "name_jp": "キャメル・クラフト・3・ボックス", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1115", + "image_url": "https://ribenyan.com/data/images/goods/77_68f099bc1184a.webp", + "name_cn": "骆驼/Camel Craft 薄荷 5mg 硬盒", + "name_jp": "キャメル・クラフト・メンソール・5・ボックス", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1174", + "image_url": "https://ribenyan.com/data/images/goods/84_68f098ef0a9cd.webp", + "name_cn": "骆驼/Camel Craft 1mg 长支 硬盒", + "name_jp": "キャメル・クラフト・1・100’s・ボックス", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1058", + "image_url": "https://ribenyan.com/data/images/goods/86_68f098ef57a87.webp", + "name_cn": "骆驼/Camel Craft 薄荷 莓果爆珠 1mg 长支 硬盒", + "name_jp": "キャメル・クラフト・メンソールベリーカプセル1・100’sボックス", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1366", + "image_url": "https://ribenyan.com/data/images/goods/90_68f098ef98b72.webp", + "name_cn": "骆驼/Camel Craft 薄荷 5mg 长支 细支", + "name_jp": "キャメル・クラフト・メンソール・5・100’s・スリム", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1175", + "image_url": "https://ribenyan.com/data/images/goods/78_68f099bc20be8.webp", + "name_cn": "骆驼/Camel Craft 薄荷 1mg 长支 硬盒", + "name_jp": "キャメル・クラフト・メンソール・1・100’s・ボックス", + "description": "", + "price_jpy": 450, + "category": "Camel/骆驼", + "price_cny": 21.6 + }, + { + "external_id": "1971", + "image_url": "https://ribenyan.com/data/images/goods/106_68f098f3d03fb.webp", + "name_cn": "回声/Echo 香烟", + "name_jp": "エコー・KS", + "description": "", + "price_jpy": 500, + "category": "Echo/回声", + "price_cny": 24.0 + }, + { + "external_id": "4479", + "image_url": "https://ribenyan.com/data/images/goods/706_694d3b989e054.webp", + "name_cn": "Floyd 6", + "name_jp": "フロイド 6", + "description": "", + "price_jpy": 540, + "category": "FLOYD", + "price_cny": 25.92 + }, + { + "external_id": "4478", + "image_url": "https://ribenyan.com/data/images/goods/705_694d3b803758d.webp", + "name_cn": "Floyd 1", + "name_jp": "フロイド 1", + "description": "", + "price_jpy": 540, + "category": "FLOYD", + "price_cny": 25.92 + }, + { + "external_id": "1027", + "image_url": "https://ribenyan.com/data/images/goods/96_68f0999b97c4f.webp", + "name_cn": "喜力/Hi-lite 17mg 软盒", + "name_jp": "ハイライト", + "description": "", + "price_jpy": 520, + "category": "Hi-lite/喜力", + "price_cny": 24.96 + }, + { + "external_id": "1392", + "image_url": "https://ribenyan.com/data/images/goods/97_68f0999ba771d.webp", + "name_cn": "喜力/Hi-lite 薄荷 10mg", + "name_jp": "ハイライト・メンソール", + "description": "", + "price_jpy": 520, + "category": "Hi-lite/喜力", + "price_cny": 24.96 + }, + { + "external_id": "1016", + "image_url": "https://ribenyan.com/data/images/goods/67_68f098eac4c34.webp", + "name_cn": "希望/Hope 原味 14mg 10支 硬盒", + "name_jp": "ホープ(10本入)", + "description": "每条20包", + "price_jpy": 300, + "category": "Hope/希望", + "price_cny": 14.4 + }, + { + "external_id": "1267", + "image_url": "https://ribenyan.com/data/images/goods/69_68f098eb00210.webp", + "name_cn": "希望/Hope 淡味 9mg 10支 硬盒", + "name_jp": "ホープ・ライト", + "description": "每条20包", + "price_jpy": 300, + "category": "Hope/希望", + "price_cny": 14.4 + }, + { + "external_id": "1415", + "image_url": "https://ribenyan.com/data/images/goods/71_68f098eb23792.webp", + "name_cn": "希望/Hope 超淡 6mg 10支 硬盒", + "name_jp": "ホープ・スーパー・ライト", + "description": "每条20包", + "price_jpy": 300, + "category": "Hope/希望", + "price_cny": 14.4 + }, + { + "external_id": "1693", + "image_url": "https://ribenyan.com/data/images/goods/85_68f098ef1b6b0.webp", + "name_cn": "梅比乌斯/MEVIUS Premium薄荷 紫色爆珠 8mg 硬盒", + "name_jp": "メビウス・プレミアムメンソール・オプション・パープル・8", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1355", + "image_url": "https://ribenyan.com/data/images/goods/9_68f098dc343df.webp", + "name_cn": "梅比乌斯/MEVIUS 特淡 3mg 软盒", + "name_jp": "メビウス・エクストラライト", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1688", + "image_url": "https://ribenyan.com/data/images/goods/25_68f098e1157af.webp", + "name_cn": "梅比乌斯/MEVIUS Premium薄荷 紫色爆珠 5mg 硬盒", + "name_jp": "メビウス・プレミアムメンソール・オプション・パープル・5", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1706", + "image_url": "https://ribenyan.com/data/images/goods/27_68f098e1958ed.webp", + "name_cn": "梅比乌斯/MEVIUS Premium薄荷 黄色爆珠 5mg 硬盒", + "name_jp": "メビウス・プレミアムメンソール・オプション・イエロー・5", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1041", + "image_url": "https://ribenyan.com/data/images/goods/41_69c91a77e70a7.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 薄荷 紫色爆珠 5mg 长支 细支", + "name_jp": "メビウス・イーシリーズ・メンソールOPパープル5・100’sスリム", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1223", + "image_url": "https://ribenyan.com/data/images/goods/6_68f098db99f25.webp", + "name_cn": "梅比乌斯/MEVIUS 超淡 6mg 软盒", + "name_jp": "メビウス・スーパーライト", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1060", + "image_url": "https://ribenyan.com/data/images/goods/2_68f098daef6c3.webp", + "name_cn": "梅比乌斯/MEVIUS RICH 12mg 软盒", + "name_jp": "メビウス・リッチ", + "description": "5月份停产,库存售罄下架。", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1372", + "image_url": "https://ribenyan.com/data/images/goods/16_68f0999131b5d.webp", + "name_cn": "梅比乌斯/MEVIUS 1mg 软盒", + "name_jp": "メビウス・1", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1023", + "image_url": "https://ribenyan.com/data/images/goods/4_68f098db456a7.webp", + "name_cn": "梅比乌斯/MEVIUS 10mg 软盒", + "name_jp": "メビウス", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1705", + "image_url": "https://ribenyan.com/data/images/goods/28_68f098e1d1b56.webp", + "name_cn": "梅比乌斯/MEVIUS Premium薄荷 黄色爆珠 1mg 长支 硬盒", + "name_jp": "メビウス・プレミアムメンソール・オプション・イエロー・ワン・100", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1042", + "image_url": "https://ribenyan.com/data/images/goods/42_68f098e5d1ed2.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 薄荷 紫色爆珠 1mg 长支 细支", + "name_jp": "メビウス・イーシリーズ・メンソールOPパープル1・100’sスリム", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1692", + "image_url": "https://ribenyan.com/data/images/goods/26_68f098e155c47.webp", + "name_cn": "梅比乌斯/MEVIUS Premium薄荷 紫色爆珠 1mg 长支 硬盒", + "name_jp": "メビウス・プレメン・オプション・パープル・1・100’s", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1088", + "image_url": "https://ribenyan.com/data/images/goods/43_68f098e616aea.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 薄荷 冰风暴 1mg 长支 细支", + "name_jp": "メビウス・イーシリーズ[メンソール]アイスストーム・ワン・100s・スリム", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1397", + "image_url": "https://ribenyan.com/data/images/goods/31_68f098e20d417.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 薄荷 紫色爆珠 8mg 硬盒", + "name_jp": "メビウス・イーシリーズ・メンソール・オプション・パープル・8", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1138", + "image_url": "https://ribenyan.com/data/images/goods/11_68f098dc7dccb.webp", + "name_cn": "梅比乌斯/MEVIUS 淡味 8mg 软盒", + "name_jp": "メビウス・ライト", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1964", + "image_url": "https://ribenyan.com/data/images/goods/34_68f099a5c8072.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 薄荷 冰风暴 8mg 硬盒", + "name_jp": "メビウス・イーシリーズ・アイスストーム・8", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1440", + "image_url": "https://ribenyan.com/data/images/goods/32_68f099a53a372.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 薄荷 紫色爆珠 5mg 硬盒", + "name_jp": "メビウス・イーシリーズ・メンソール・オプション・パープル・5", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1965", + "image_url": "https://ribenyan.com/data/images/goods/35_68f099a5d7c24.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 薄荷 冰风暴 5mg 硬盒", + "name_jp": "メビウス・イーシリーズ・アイスストーム・5", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1310", + "image_url": "https://ribenyan.com/data/images/goods/14_68f09990f3890.webp", + "name_cn": "梅比乌斯/MEVIUS 特淡 3mg 硬盒", + "name_jp": "メビウス・エクストラライト・ボックス", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1356", + "image_url": "https://ribenyan.com/data/images/goods/17_69c91a97c5866.webp", + "name_cn": "梅比乌斯/MEVIUS 1mg 硬盒", + "name_jp": "メビウス・1・ボックス", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1343", + "image_url": "https://ribenyan.com/data/images/goods/18_68f09991aa274.webp", + "name_cn": "梅比乌斯/MEVIUS 1mg 长支 硬盒", + "name_jp": "メビウス・ワン・100s・ボックス", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1329", + "image_url": "https://ribenyan.com/data/images/goods/15_68f09991248c1.webp", + "name_cn": "梅比乌斯/MEVIUS 特淡 3mg 长支 硬盒", + "name_jp": "メビウス・エクストラライト・100s・ボックス", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1734", + "image_url": "https://ribenyan.com/data/images/goods/723_69106a34be258.webp", + "name_cn": "梅比乌斯/MEVIUS Strong 1mg 长支", + "name_jp": "メビウス・ ストロング・ワン・ 100’", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1381", + "image_url": "https://ribenyan.com/data/images/goods/10_68f098dc6d278.webp", + "name_cn": "梅比乌斯/MEVIUS 10mg 长支 硬盒", + "name_jp": "メビウス・100’s・ボックス", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1286", + "image_url": "https://ribenyan.com/data/images/goods/5_68f098db739d5.webp", + "name_cn": "梅比乌斯/MEVIUS 10mg 硬盒", + "name_jp": "メビウス・ボックス", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1064", + "image_url": "https://ribenyan.com/data/images/goods/38_68f099a63810d.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 6mg 长支 硬盒", + "name_jp": "メビウス・イーシリーズ・6・100’s・スリム", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1061", + "image_url": "https://ribenyan.com/data/images/goods/3_68f098db20d52.webp", + "name_cn": "梅比乌斯/MEVIUS RICH 12mg 硬盒", + "name_jp": "メビウス・リッチ・ ボックス", + "description": "5月份停产,库存售罄下架。", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1087", + "image_url": "https://ribenyan.com/data/images/goods/39_68f099a6c19d8.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 3mg 长支 硬盒", + "name_jp": "メビウス・イーシリーズ・3・100’s・スリム", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1063", + "image_url": "https://ribenyan.com/data/images/goods/37_68f099a610536.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 10mg 长支 硬盒", + "name_jp": "メビウス・イーシリーズ・10・100’s・スリム", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1462", + "image_url": "https://ribenyan.com/data/images/goods/8_68f098dc24af2.webp", + "name_cn": "梅比乌斯/MEVIUS 超淡 6mg 长支 硬盒", + "name_jp": "メビウス・スーパーライト・100’s・ボックス", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1065", + "image_url": "https://ribenyan.com/data/images/goods/40_68f099a71ac55.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 1mg 长支 硬盒", + "name_jp": "メビウス・イーシリーズ・ワン・100’s・スリム", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1296", + "image_url": "https://ribenyan.com/data/images/goods/7_68f098dbd84de.webp", + "name_cn": "梅比乌斯/MEVIUS 超淡 6mg 硬盒", + "name_jp": "メビウス・スーパーライト・ボックス", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1966", + "image_url": "https://ribenyan.com/data/images/goods/36_68f099a5edbe6.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 薄荷 冰风暴 1mg 长支 硬盒", + "name_jp": "メビウス・イーシリーズ・アイスストーム・ワン・100s", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1506", + "image_url": "https://ribenyan.com/data/images/goods/21_68f099921c0f0.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 10mg 硬盒", + "name_jp": "メビウス・イーシリーズ・10", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1441", + "image_url": "https://ribenyan.com/data/images/goods/33_68f099a577389.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 薄荷 紫色爆珠 1mg 长支 硬盒", + "name_jp": "メビウス・イーシリーズ・メンソールオプションパープルワン100’s", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1424", + "image_url": "https://ribenyan.com/data/images/goods/13_68f09990e5c63.webp", + "name_cn": "梅比乌斯/MEVIUS 淡味 8mg 长支 硬盒", + "name_jp": "メビウス・ライト・100s・ボックス", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1508", + "image_url": "https://ribenyan.com/data/images/goods/24_69c91b4b3643c.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 1mg 长支 硬盒", + "name_jp": "メビウス・イーシリーズ・ワン・100’s", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1412", + "image_url": "https://ribenyan.com/data/images/goods/23_68f098e09ae04.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 3mg 长支 硬盒", + "name_jp": "メビウス・イーシリーズ・3・100’s", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1507", + "image_url": "https://ribenyan.com/data/images/goods/22_68f098e05ca89.webp", + "name_cn": "梅比乌斯/MEVIUS E系列 6mg 硬盒", + "name_jp": "メビウス・イーシリーズ・6", + "description": "", + "price_jpy": 500, + "category": "MEVIUS/梅比乌斯", + "price_cny": 24.0 + }, + { + "external_id": "1299", + "image_url": "https://ribenyan.com/data/images/goods/12_68f09990bf1b6.webp", + "name_cn": "梅比乌斯/MEVIUS 淡味 8mg 硬盒", + "name_jp": "メビウス・ボックス", + "description": "", + "price_jpy": 580, + "category": "MEVIUS/梅比乌斯", + "price_cny": 27.84 + }, + { + "external_id": "1264", + "image_url": "https://ribenyan.com/data/images/goods/65_68f098eaa1d22.webp", + "name_cn": "和平/Peace 淡味 10mg 硬盒", + "name_jp": "ピース・ライト・ボックス", + "description": "", + "price_jpy": 600, + "category": "Peace/和平", + "price_cny": 28.8 + }, + { + "external_id": "1018", + "image_url": "https://ribenyan.com/data/images/goods/64_68f098ea917ff.webp", + "name_cn": "和平/Peace 黄 21mg 软盒", + "name_jp": "ピース(20本入)", + "description": "", + "price_jpy": 600, + "category": "Peace/和平", + "price_cny": 28.8 + }, + { + "external_id": "1609", + "image_url": "https://ribenyan.com/data/images/goods/72_68f099bbae538.webp", + "name_cn": "和平/Peace 10mg 铁盒", + "name_jp": "ザ・ピース", + "description": "只有散盒!铁盒外有同色纸盒", + "price_jpy": 1000, + "category": "Peace/和平", + "price_cny": 48.0 + }, + { + "external_id": "1035", + "image_url": "https://ribenyan.com/data/images/goods/61_68f09996e02b4.webp", + "name_cn": "和平/Peace 无嘴 28mg 50支 罐装", + "name_jp": "ピース(50本入)", + "description": "", + "price_jpy": 1500, + "category": "Peace/和平", + "price_cny": 72.0 + }, + { + "external_id": "1919", + "image_url": "https://ribenyan.com/data/images/goods/74_68f099bbc8488.webp", + "name_cn": "和平/Peace 小雪茄", + "name_jp": "ピース・リトルシガー", + "description": "", + "price_jpy": 650, + "category": "Peace/和平", + "price_cny": 31.2 + }, + { + "external_id": "1034", + "image_url": "https://ribenyan.com/data/images/goods/63_68f098ea81219.webp", + "name_cn": "和平/Peace 无嘴 28mg 10支 硬盒", + "name_jp": "ピース(10本入)", + "description": "每条20包", + "price_jpy": 300, + "category": "Peace/和平", + "price_cny": 14.4 + }, + { + "external_id": "1333", + "image_url": "https://ribenyan.com/data/images/goods/66_68f098eab1dcf.webp", + "name_cn": "和平/Peace 超淡 6mg 硬盒", + "name_jp": "ピース・スーパーライト・ボックス", + "description": "", + "price_jpy": 600, + "category": "Peace/和平", + "price_cny": 28.8 + }, + { + "external_id": "1446", + "image_url": "https://ribenyan.com/data/images/goods/108_68f098f3ef5f2.webp", + "name_cn": "碧丝梦/Pianissmo Petil 薄荷 1mg", + "name_jp": "ピアニッシモ・ペティル・メンソール", + "description": "", + "price_jpy": 570, + "category": "Pianissmo/碧丝梦", + "price_cny": 27.36 + }, + { + "external_id": "1613", + "image_url": "https://ribenyan.com/data/images/goods/110_68f098f41eaed.webp", + "name_cn": "碧丝梦/Pianissmo Precia 薄荷 1mg", + "name_jp": "ピアニッシモ・プレシア・メンソール", + "description": "", + "price_jpy": 570, + "category": "Pianissmo/碧丝梦", + "price_cny": 27.36 + }, + { + "external_id": "1487", + "image_url": "https://ribenyan.com/data/images/goods/107_68f098f3df5db.webp", + "name_cn": "碧丝梦/Pianissmo Aria 薄荷 1mg", + "name_jp": "ピアニッシモ・アリア・メンソール", + "description": "", + "price_jpy": 570, + "category": "Pianissmo/碧丝梦", + "price_cny": 27.36 + }, + { + "external_id": "1379", + "image_url": "https://ribenyan.com/data/images/goods/109_68f098f40d02f.webp", + "name_cn": "碧丝梦/Pianissmo Icene 薄荷 5mg", + "name_jp": "ピアニッシモ・アイシーン・メンソール・5", + "description": "", + "price_jpy": 570, + "category": "Pianissmo/碧丝梦", + "price_cny": 27.36 + }, + { + "external_id": "1020", + "image_url": "https://ribenyan.com/data/images/goods/44_68f098e65c72c.webp", + "name_cn": "七星/SevenStars 碳滤 14mg 软盒", + "name_jp": "セブンスター", + "description": "", + "price_jpy": 600, + "category": "SevenStars/七星", + "price_cny": 28.8 + }, + { + "external_id": "1244", + "image_url": "https://ribenyan.com/data/images/goods/46_68f098e67db26.webp", + "name_cn": "七星/SevenStars 碳滤 10mg 硬盒", + "name_jp": "セブンスター・10・ボックス", + "description": "", + "price_jpy": 600, + "category": "SevenStars/七星", + "price_cny": 28.8 + }, + { + "external_id": "1422", + "image_url": "https://ribenyan.com/data/images/goods/47_68f098e68f19a.webp", + "name_cn": "七星/SevenStars 碳滤 7mg 硬盒", + "name_jp": "セブンスター・7・ボックス", + "description": "", + "price_jpy": 600, + "category": "SevenStars/七星", + "price_cny": 28.8 + }, + { + "external_id": "1312", + "image_url": "https://ribenyan.com/data/images/goods/45_68f098e66d6fc.webp", + "name_cn": "七星/SevenStars 碳滤 14mg 硬盒!硬盒!硬盒!", + "name_jp": "セブンスター・ボックス", + "description": "注意是硬盒,每天都有人买错", + "price_jpy": 600, + "category": "SevenStars/七星", + "price_cny": 28.8 + }, + { + "external_id": "2831", + "image_url": "https://ribenyan.com/data/images/goods/1_68f4cdaa44e22.webp", + "name_cn": "七星/SevenStars Bold Black 18mg", + "name_jp": "セブンスター・ボールド・ブラック", + "description": "", + "price_jpy": 600, + "category": "SevenStars/七星", + "price_cny": 28.8 + }, + { + "external_id": "1624", + "image_url": "https://ribenyan.com/data/images/goods/48_68f098e69ffa5.webp", + "name_cn": "七星/SevenStars 薄荷 12mg 硬盒", + "name_jp": "セブンスター・メンソール・12", + "description": "", + "price_jpy": 600, + "category": "SevenStars/七星", + "price_cny": 28.8 + }, + { + "external_id": "2832", + "image_url": "https://ribenyan.com/data/images/goods/578_68f4cd93857ba.webp", + "name_cn": "七星/SevenStars Menthol Solid White 18mg", + "name_jp": "セプンスタ-・メンソ-ル・ソリッド・ホワイト", + "description": "", + "price_jpy": 600, + "category": "SevenStars/七星", + "price_cny": 28.8 + }, + { + "external_id": "1143", + "image_url": "https://ribenyan.com/data/images/goods/49_68f098e6af94e.webp", + "name_cn": "七星/SevenStars 薄荷 8mg 硬盒", + "name_jp": "セブンスター・メンソール・8", + "description": "", + "price_jpy": 600, + "category": "SevenStars/七星", + "price_cny": 28.8 + }, + { + "external_id": "1970", + "image_url": "https://ribenyan.com/data/images/goods/105_68f098f3c0f8e.webp", + "name_cn": "若叶/Wakaba 香烟", + "name_jp": "わかば・KS", + "description": "", + "price_jpy": 500, + "category": "Wakaba/若叶", + "price_cny": 24.0 + }, + { + "external_id": "1200", + "image_url": "https://ribenyan.com/data/images/goods/50_68f098e6c0178.webp", + "name_cn": "云斯顿/Winston Caster 白 5mg 软盒", + "name_jp": "ウィンストン・キャスター・ホワイト・5", + "description": "", + "price_jpy": 540, + "category": "Winston/云斯顿", + "price_cny": 25.92 + }, + { + "external_id": "1470", + "image_url": "https://ribenyan.com/data/images/goods/62_68f098ea6d14f.webp", + "name_cn": "云斯顿/Winston 红 12mg 软盒", + "name_jp": "ウィンストン", + "description": "", + "price_jpy": 530, + "category": "Winston/云斯顿", + "price_cny": 25.44 + }, + { + "external_id": "1278", + "image_url": "https://ribenyan.com/data/images/goods/51_68f098e6cf925.webp", + "name_cn": "云斯顿/Winston Caster 白 5mg 硬盒", + "name_jp": "ウィンストン・キャスター・ホワイト・5・ボックス", + "description": "", + "price_jpy": 540, + "category": "Winston/云斯顿", + "price_cny": 25.92 + }, + { + "external_id": "1197", + "image_url": "https://ribenyan.com/data/images/goods/55_68f099967424e.webp", + "name_cn": "云斯顿/Winston Cabin 红 8mg 硬盒", + "name_jp": "ウィンストン・キャビン・レッド・8・ボックス", + "description": "", + "price_jpy": 540, + "category": "Winston/云斯顿", + "price_cny": 25.92 + }, + { + "external_id": "1315", + "image_url": "https://ribenyan.com/data/images/goods/53_69c91afd817c0.webp", + "name_cn": "云斯顿/Winston Caster 白 3mg 硬盒", + "name_jp": "ウィンストン・キャスター・ホワイト・3・ボックス", + "description": "", + "price_jpy": 540, + "category": "Winston/云斯顿", + "price_cny": 25.92 + }, + { + "external_id": "1334", + "image_url": "https://ribenyan.com/data/images/goods/56_68f0999688713.webp", + "name_cn": "云斯顿/Winston Caster 白 1mg 长支 硬盒", + "name_jp": "ウィンストン・キャスター・ホワイト・ワン・100s・ボックス", + "description": "", + "price_jpy": 540, + "category": "Winston/云斯顿", + "price_cny": 25.92 + }, + { + "external_id": "1245", + "image_url": "https://ribenyan.com/data/images/goods/58_68f09996aa292.webp", + "name_cn": "云斯顿/Winston Cabin 红 5mg 硬盒", + "name_jp": "ウィンストン・キャビン・レッド・5・ボックス", + "description": "", + "price_jpy": 540, + "category": "Winston/云斯顿", + "price_cny": 25.92 + }, + { + "external_id": "1469", + "image_url": "https://ribenyan.com/data/images/goods/60_68f09996ce1ba.webp", + "name_cn": "云斯顿/Winston Cabin 红 1mg 硬盒", + "name_jp": "ウィンストン・キャビン・レッド・ワン・100s・ボックス", + "description": "", + "price_jpy": 540, + "category": "Winston/云斯顿", + "price_cny": 25.92 + }, + { + "external_id": "1032", + "image_url": "https://ribenyan.com/data/images/goods/442_69494da1dcc7d.webp", + "name_cn": "海马", + "name_jp": "ウルマ", + "description": "", + "price_jpy": 530, + "category": "海马/ウルマ", + "price_cny": 25.44 + }, + { + "external_id": "2385", + "image_url": "https://ribenyan.com/data/images/goods/315_694d37dd48053.webp", + "name_cn": "Apache Red", + "name_jp": "アパッチ・レッド", + "description": "", + "price_jpy": 480, + "category": "APACHE", + "price_cny": 23.04 + }, + { + "external_id": "2381", + "image_url": "https://ribenyan.com/data/images/goods/314_694d37cba1c2b.webp", + "name_cn": "Apache Blue", + "name_jp": "アパッチ・ブルー", + "description": "", + "price_jpy": 480, + "category": "APACHE", + "price_cny": 23.04 + }, + { + "external_id": "3010", + "image_url": "https://ribenyan.com/data/images/goods/230_690c68b382ab1.webp", + "name_cn": "老船长/Ark Royal Sweet", + "name_jp": "アーク・ローヤル・スイート", + "description": "", + "price_jpy": 540, + "category": "Ark Royal/老船长", + "price_cny": 25.92 + }, + { + "external_id": "2742", + "image_url": "https://ribenyan.com/data/images/goods/233_690c691ab82a5.webp", + "name_cn": "老船长/Ark Royal Black Chocolate 黑巧克力", + "name_jp": "アークローヤル・ブラック・チョコレート", + "description": "", + "price_jpy": 540, + "category": "Ark Royal/老船长", + "price_cny": 25.92 + }, + { + "external_id": "3041", + "image_url": "https://ribenyan.com/data/images/goods/229_690c6891f2faf.webp", + "name_cn": "老船长/Ark Royal Apple Mint 苹果薄荷", + "name_jp": "アーク・ローヤル・アップル・ミント", + "description": "", + "price_jpy": 540, + "category": "Ark Royal/老船长", + "price_cny": 25.92 + }, + { + "external_id": "2907", + "image_url": "https://ribenyan.com/data/images/goods/231_690c68df4acfa.webp", + "name_cn": "老船长/Ark Royal Paradise Tea", + "name_jp": "アーク・ローヤル・パラダイス・ティー", + "description": "", + "price_jpy": 540, + "category": "Ark Royal/老船长", + "price_cny": 25.92 + }, + { + "external_id": "2580", + "image_url": "https://ribenyan.com/data/images/goods/228_690c68450083a.webp", + "name_cn": "老船长/Ark Royal Pipe", + "name_jp": "アーク・ローヤル", + "description": "", + "price_jpy": 540, + "category": "Ark Royal/老船长", + "price_cny": 25.92 + }, + { + "external_id": "2188", + "image_url": "https://ribenyan.com/data/images/goods/232_690c6900d4147.webp", + "name_cn": "老船长/Ark Royal Paradise Tea 细支 1mg", + "name_jp": "アーク・ローヤル・パラダイス・ティー・スリム・1", + "description": "", + "price_jpy": 520, + "category": "Ark Royal/老船长", + "price_cny": 24.96 + }, + { + "external_id": "2589", + "image_url": "https://ribenyan.com/data/images/goods/350_694d3bbc4e3cf.webp", + "name_cn": "宾利/Bentley Black", + "name_jp": "ベントレー・ブラック", + "description": "", + "price_jpy": 700, + "category": "Bentley/宾利", + "price_cny": 33.6 + }, + { + "external_id": "2588", + "image_url": "https://ribenyan.com/data/images/goods/352_694d3bacde601.webp", + "name_cn": "宾利/Bentley Gold", + "name_jp": "ベントレー・ゴールド", + "description": "", + "price_jpy": 700, + "category": "Bentley/宾利", + "price_cny": 33.6 + }, + { + "external_id": "2590", + "image_url": "https://ribenyan.com/data/images/goods/351_694d3bcf12ae1.webp", + "name_cn": "宾利/Bentley 薄荷", + "name_jp": "ベントレー・メンソール", + "description": "", + "price_jpy": 700, + "category": "Bentley/宾利", + "price_cny": 33.6 + }, + { + "external_id": "2822", + "image_url": "https://ribenyan.com/data/images/goods/788_694d306538d65.webp", + "name_cn": "黑魔鬼/BlackDevil 可可 限量版", + "name_jp": "ブラックデビル カカオ リミテッドエディション", + "description": "", + "price_jpy": 560, + "category": "BlackDevil/黑魔鬼", + "price_cny": 26.88 + }, + { + "external_id": "3741", + "image_url": "https://ribenyan.com/data/images/goods/259_690c70660576c.webp", + "name_cn": "黑魔鬼/BlackDevil 原味", + "name_jp": "ブラックデビル・オリジナル", + "description": "", + "price_jpy": 560, + "category": "BlackDevil/黑魔鬼", + "price_cny": 26.88 + }, + { + "external_id": "2420", + "image_url": "https://ribenyan.com/data/images/goods/263_690c70be26ad1.webp", + "name_cn": "黑魔鬼/BlackDevil 粉红香草", + "name_jp": "ブラックデビル・ピンクバニラ", + "description": "", + "price_jpy": 560, + "category": "BlackDevil/黑魔鬼", + "price_cny": 26.88 + }, + { + "external_id": "3744", + "image_url": "https://ribenyan.com/data/images/goods/265_690c70f7c1abb.webp", + "name_cn": "黑魔鬼/BlackDevil 摩卡香草", + "name_jp": "ブラックデビル・モカバニラ", + "description": "", + "price_jpy": 560, + "category": "BlackDevil/黑魔鬼", + "price_cny": 26.88 + }, + { + "external_id": "2581", + "image_url": "https://ribenyan.com/data/images/goods/262_690c70a06aa46.webp", + "name_cn": "黑魔鬼/BlackDevil 黄金特别版", + "name_jp": "ブラックデビル・ゴールドスペシャル", + "description": "", + "price_jpy": 560, + "category": "BlackDevil/黑魔鬼", + "price_cny": 26.88 + }, + { + "external_id": "3742", + "image_url": "https://ribenyan.com/data/images/goods/260_690c708278653.webp", + "name_cn": "黑魔鬼/BlackDevil 可可", + "name_jp": "ブラックデビル・カカオ", + "description": "", + "price_jpy": 560, + "category": "BlackDevil/黑魔鬼", + "price_cny": 26.88 + }, + { + "external_id": "3743", + "image_url": "https://ribenyan.com/data/images/goods/264_690c70df9d22b.webp", + "name_cn": "黑魔鬼/BlackDevil 薄荷香草", + "name_jp": "ブラックデビル・ミント・バニラ", + "description": "", + "price_jpy": 560, + "category": "BlackDevil/黑魔鬼", + "price_cny": 26.88 + }, + { + "external_id": "3836", + "image_url": "https://ribenyan.com/data/images/goods/258_690c702b93d6c.webp", + "name_cn": "黑魔鬼/BlackDevil 北极薄荷", + "name_jp": "ブラックデビル・アークティックメンソール", + "description": "", + "price_jpy": 560, + "category": "BlackDevil/黑魔鬼", + "price_cny": 26.88 + }, + { + "external_id": "2382", + "image_url": "https://ribenyan.com/data/images/goods/253_690c6f3069114.webp", + "name_cn": "黑杰克/BlackJack Supasawa Pineapple 8mg (菠萝)", + "name_jp": "ブラックジャック・スパサワ・パイナップル8", + "description": "", + "price_jpy": 500, + "category": "BlackJack/黑杰克", + "price_cny": 24.0 + }, + { + "external_id": "2626", + "image_url": "https://ribenyan.com/data/images/goods/251_690c6fcc63e0e.webp", + "name_cn": "黑杰克/BlackJack Supasawa Random 5mg (5种爆珠随机)", + "name_jp": "ブラックジャック・スパサワ・ランダム5", + "description": "", + "price_jpy": 550, + "category": "BlackJack/黑杰克", + "price_cny": 26.4 + }, + { + "external_id": "2383", + "image_url": "https://ribenyan.com/data/images/goods/255_690c6f6c0d75a.webp", + "name_cn": "黑杰克/BlackJack Supasawa Lime 8mg (青柠)", + "name_jp": "ブラックジャック・スパサワ・ライム8", + "description": "", + "price_jpy": 500, + "category": "BlackJack/黑杰克", + "price_cny": 24.0 + }, + { + "external_id": "2547", + "image_url": "https://ribenyan.com/data/images/goods/254_690c6f4dc22a3.webp", + "name_cn": "黑杰克/BlackJack Supasawa Mango 5mg (芒果)", + "name_jp": "ブラックジャック・スパサワ・マンゴー5", + "description": "", + "price_jpy": 500, + "category": "BlackJack/黑杰克", + "price_cny": 24.0 + }, + { + "external_id": "2384", + "image_url": "https://ribenyan.com/data/images/goods/257_690c700b7e7a1.webp", + "name_cn": "黑杰克/BlackJack Supasawa Lemon 8mg (柠檬)", + "name_jp": "ブラックジャック・スパサワ・レモン8", + "description": "", + "price_jpy": 500, + "category": "BlackJack/黑杰克", + "price_cny": 24.0 + }, + { + "external_id": "2545", + "image_url": "https://ribenyan.com/data/images/goods/252_690c6f16a807b.webp", + "name_cn": "黑杰克/BlackJack Supasawa Grapefruit 5mg (葡萄柚)", + "name_jp": "ブラックジャック・スパサワ・グレープフルーツ5", + "description": "", + "price_jpy": 500, + "category": "BlackJack/黑杰克", + "price_cny": 24.0 + }, + { + "external_id": "2548", + "image_url": "https://ribenyan.com/data/images/goods/256_690c6ff0e5059.webp", + "name_cn": "黑杰克/BlackJack Supasawa Lemon 5mg (柠檬)", + "name_jp": "ブラックジャック・スパサワ・レモン5", + "description": "", + "price_jpy": 500, + "category": "BlackJack/黑杰克", + "price_cny": 24.0 + }, + { + "external_id": "2309", + "image_url": "https://ribenyan.com/data/images/goods/300_690d69b755f1d.webp", + "name_cn": "黑杰克/BlackJack Island Black", + "name_jp": "ブラックジャック・アイランドブラック", + "description": "", + "price_jpy": 490, + "category": "BlackJack/黑杰克", + "price_cny": 23.52 + }, + { + "external_id": "3993", + "image_url": "https://ribenyan.com/data/images/goods/311_690d6b33ce2e5.webp", + "name_cn": "黑杰克/BlackJack Compact+Change", + "name_jp": "ブラックジャック・コンパクト+チェンジ", + "description": "", + "price_jpy": 490, + "category": "BlackJack/黑杰克", + "price_cny": 23.52 + }, + { + "external_id": "3992", + "image_url": "https://ribenyan.com/data/images/goods/312_690d6b077d916.webp", + "name_cn": "黑杰克/BlackJack Compact Black", + "name_jp": "ブラックジャック・コンパクトブラック", + "description": "", + "price_jpy": 490, + "category": "BlackJack/黑杰克", + "price_cny": 23.52 + }, + { + "external_id": "3745", + "image_url": "https://ribenyan.com/data/images/goods/301_690d69f4677dd.webp", + "name_cn": "黑杰克/BlackJack Ocean Mint", + "name_jp": "ブラックジャック・オーシャンミント", + "description": "", + "price_jpy": 490, + "category": "BlackJack/黑杰克", + "price_cny": 23.52 + }, + { + "external_id": "2308", + "image_url": "https://ribenyan.com/data/images/goods/307_690d69969f963.webp", + "name_cn": "黑杰克/BlackJack Island Brown", + "name_jp": "ブラックジャック・アイランドブラウン", + "description": "", + "price_jpy": 490, + "category": "BlackJack/黑杰克", + "price_cny": 23.52 + }, + { + "external_id": "2307", + "image_url": "https://ribenyan.com/data/images/goods/308_690d69d4637ab.webp", + "name_cn": "黑杰克/BlackJack Ocean Blue", + "name_jp": "ブラックジャック・オーシャンブルー", + "description": "", + "price_jpy": 490, + "category": "BlackJack/黑杰克", + "price_cny": 23.52 + }, + { + "external_id": "3746", + "image_url": "https://ribenyan.com/data/images/goods/309_690d6a975f8cb.webp", + "name_cn": "黑杰克/BlackJack Ocean+Change", + "name_jp": "ブラックジャック・オーシャン+チェンジ", + "description": "", + "price_jpy": 490, + "category": "BlackJack/黑杰克", + "price_cny": 23.52 + }, + { + "external_id": "3991", + "image_url": "https://ribenyan.com/data/images/goods/310_690d6ad8ae2c1.webp", + "name_cn": "黑杰克/BlackJack Compact Brown", + "name_jp": "ブラックジャック・コンパクトブラウン", + "description": "", + "price_jpy": 490, + "category": "BlackJack/黑杰克", + "price_cny": 23.52 + }, + { + "external_id": "2837", + "image_url": "https://ribenyan.com/data/images/goods/795_694d47fda03bb.webp", + "name_cn": "BlackStone Red Wine", + "name_jp": "ブラック・ストーン・ レッド・ワイン", + "description": "", + "price_jpy": 670, + "category": "BlackStone/黑石", + "price_cny": 32.16 + }, + { + "external_id": "2836", + "image_url": "https://ribenyan.com/data/images/goods/790_694d47f186a6c.webp", + "name_cn": "BlackStone Classic Vanilla", + "name_jp": "ブラック・ストーン・ クラシック・バニラ", + "description": "", + "price_jpy": 670, + "category": "BlackStone/黑石", + "price_cny": 32.16 + }, + { + "external_id": "2838", + "image_url": "https://ribenyan.com/data/images/goods/796_694d48084b60e.webp", + "name_cn": "BlackStone Wild Strawberry", + "name_jp": "ブラック・ストーン・ ワイルド・ストロベリー", + "description": "", + "price_jpy": 670, + "category": "BlackStone/黑石", + "price_cny": 32.16 + }, + { + "external_id": "2835", + "image_url": "https://ribenyan.com/data/images/goods/789_694d47e2718af.webp", + "name_cn": "BlackStone American Cherry", + "name_jp": "ブラック・ストーン・ アメリカン・チェリー", + "description": "", + "price_jpy": 670, + "category": "BlackStone/黑石", + "price_cny": 32.16 + }, + { + "external_id": "4347", + "image_url": "https://ribenyan.com/data/images/goods/271_690c7199bf745.webp", + "name_cn": "宝亨/Bohem Cigar Mojito Double 5mg", + "name_jp": "ボヘーム・シガー・モヒート・ダブル", + "description": "", + "price_jpy": 520, + "category": "Bohem/宝亨", + "price_cny": 24.96 + }, + { + "external_id": "4137", + "image_url": "https://ribenyan.com/data/images/goods/269_690c7160c5593.webp", + "name_cn": "宝亨/Bohem Cigar No.6 6mg", + "name_jp": "ボヘーム・シガー・No6", + "description": "", + "price_jpy": 520, + "category": "Bohem/宝亨", + "price_cny": 24.96 + }, + { + "external_id": "4243", + "image_url": "https://ribenyan.com/data/images/goods/270_690c717ea40d2.webp", + "name_cn": "宝亨/Bohem Cigar Mojito 6mg", + "name_jp": "ボヘーム・シガー・モヒート", + "description": "", + "price_jpy": 520, + "category": "Bohem/宝亨", + "price_cny": 24.96 + }, + { + "external_id": "4372", + "image_url": "https://ribenyan.com/data/images/goods/944_69be35b597b03.webp", + "name_cn": "宝亨/Bohem Cigar Mini Super Slim Sepia", + "name_jp": "ボヘーム・シガーミニスーパースリムセピア", + "description": "", + "price_jpy": 520, + "category": "Bohem/宝亨", + "price_cny": 24.96 + }, + { + "external_id": "4458", + "image_url": "https://ribenyan.com/data/images/goods/272_690c71b892a90.webp", + "name_cn": "宝亨/Bohem Cigar Mojito One 1mg", + "name_jp": "ボヘーム・シガー・モヒート・ワン", + "description": "", + "price_jpy": 520, + "category": "Bohem/宝亨", + "price_cny": 24.96 + }, + { + "external_id": "4576", + "image_url": "https://ribenyan.com/data/images/goods/945_69be36353fb99.webp", + "name_cn": "宝亨/Bohem Cigar Grip", + "name_jp": "ボヘーム・シガーグリップ", + "description": "", + "price_jpy": 520, + "category": "Bohem/宝亨", + "price_cny": 24.96 + }, + { + "external_id": "4371", + "image_url": "https://ribenyan.com/data/images/goods/946_69be36c2be721.webp", + "name_cn": "宝亨/Bohem Cigar Mini Super Slim Ivory", + "name_jp": "ボヘーム・シガーミニ スーパースリムアイボリー", + "description": "", + "price_jpy": 520, + "category": "Bohem/宝亨", + "price_cny": 24.96 + }, + { + "external_id": "2337", + "image_url": "https://ribenyan.com/data/images/goods/249_690c6edf093d8.webp", + "name_cn": "Bright Long 1", + "name_jp": "ブライト・ロング・1", + "description": "", + "price_jpy": 460, + "category": "BRIGHT", + "price_cny": 22.08 + }, + { + "external_id": "2338", + "image_url": "https://ribenyan.com/data/images/goods/250_690c6efbb535d.webp", + "name_cn": "Bright Long 5", + "name_jp": "ブライト・ロング・5", + "description": "", + "price_jpy": 460, + "category": "BRIGHT", + "price_cny": 22.08 + }, + { + "external_id": "3715", + "image_url": "https://ribenyan.com/data/images/goods/807_695fad796c3ca.webp", + "name_cn": "Chapman Super Slim Cherry", + "name_jp": "チャップマン·ス一バ-スリム・チエリ一", + "description": "", + "price_jpy": 590, + "category": "CHAPMAN", + "price_cny": 28.32 + }, + { + "external_id": "3716", + "image_url": "https://ribenyan.com/data/images/goods/808_695fadf30293c.webp", + "name_cn": "Chapman Super Slim Vanilla", + "name_jp": "チャッフマンス-バ-スリム・バニラ", + "description": "", + "price_jpy": 590, + "category": "CHAPMAN", + "price_cny": 28.32 + }, + { + "external_id": "4290", + "image_url": "https://ribenyan.com/data/images/goods/239_690c6a26d5cf6.webp", + "name_cn": "Che/切格瓦拉 红色", + "name_jp": "チェ・レッド", + "description": "", + "price_jpy": 550, + "category": "Che/切格瓦拉", + "price_cny": 26.4 + }, + { + "external_id": "4288", + "image_url": "https://ribenyan.com/data/images/goods/236_690c699ec12e9.webp", + "name_cn": "Che/切格瓦拉 黑色", + "name_jp": "チェ・ブラック", + "description": "", + "price_jpy": 550, + "category": "Che/切格瓦拉", + "price_cny": 26.4 + }, + { + "external_id": "4410", + "image_url": "https://ribenyan.com/data/images/goods/237_690c69b63e691.webp", + "name_cn": "Che/切格瓦拉 黑色 薄荷", + "name_jp": "チェ・ブラック・メンソール", + "description": "", + "price_jpy": 550, + "category": "Che/切格瓦拉", + "price_cny": 26.4 + }, + { + "external_id": "4289", + "image_url": "https://ribenyan.com/data/images/goods/238_690c69d39b7d0.webp", + "name_cn": "Che/切格瓦拉 Blanco", + "name_jp": "チェ・ブランコ", + "description": "", + "price_jpy": 550, + "category": "Che/切格瓦拉", + "price_cny": 26.4 + }, + { + "external_id": "4503", + "image_url": "https://ribenyan.com/data/images/goods/171_690c69828db40.webp", + "name_cn": "Che/切格瓦拉 绿色 薄荷", + "name_jp": "チェ・グリーン・メンソール", + "description": "", + "price_jpy": 550, + "category": "Che/切格瓦拉", + "price_cny": 26.4 + }, + { + "external_id": "4584", + "image_url": "https://ribenyan.com/data/images/goods/240_690c6a4848510.webp", + "name_cn": "Che/切格瓦拉 1mg 薄荷", + "name_jp": "チェ・ワン・メンソール", + "description": "", + "price_jpy": 550, + "category": "Che/切格瓦拉", + "price_cny": 26.4 + }, + { + "external_id": "2230", + "image_url": "https://ribenyan.com/data/images/goods/185_694d397c0f24b.webp", + "name_cn": "卡比龙/Cigaronne 皇家 细支 黑色", + "name_jp": "シガローネロイヤルスリム・ブラック", + "description": "", + "price_jpy": 1200, + "category": "Cigaronne/卡比龙", + "price_cny": 57.6 + }, + { + "external_id": "2231", + "image_url": "https://ribenyan.com/data/images/goods/132_68f0f8e685ca6.webp", + "name_cn": "卡比龙/Cigaronne Royal Slims 薄荷", + "name_jp": "シガローネ・メンソール", + "description": "", + "price_jpy": 1600, + "category": "Cigaronne/卡比龙", + "price_cny": 76.8 + }, + { + "external_id": "2089", + "image_url": "https://ribenyan.com/data/images/goods/326_694d390aaca29.webp", + "name_cn": "卡比龙/Cigaronne Exclusive 棕色", + "name_jp": "シガローネ・エクスクルーシブ・ブラウン", + "description": "", + "price_jpy": 1500, + "category": "Cigaronne/卡比龙", + "price_cny": 72.0 + }, + { + "external_id": "2465", + "image_url": "https://ribenyan.com/data/images/goods/329_694d395cbbe51.webp", + "name_cn": "卡比龙/Cigaronne Magnet", + "name_jp": "シガローネ・マグネット", + "description": "", + "price_jpy": 800, + "category": "Cigaronne/卡比龙", + "price_cny": 38.4 + }, + { + "external_id": "2539", + "image_url": "https://ribenyan.com/data/images/goods/344_694e4272446ea.webp", + "name_cn": "卡比龙/Cigaronne Tattoo 巧克力", + "name_jp": "シガローネ・タトゥー・チョコレート", + "description": "", + "price_jpy": 800, + "category": "Cigaronne/卡比龙", + "price_cny": 38.4 + }, + { + "external_id": "2887", + "image_url": "https://ribenyan.com/data/images/goods/327_694d3942bdec6.webp", + "name_cn": "卡比龙/Cigaronne 超细 黑色", + "name_jp": "シガローネ・スーパースリム・ブラック", + "description": "", + "price_jpy": 780, + "category": "Cigaronne/卡比龙", + "price_cny": 37.44 + }, + { + "external_id": "2090", + "image_url": "https://ribenyan.com/data/images/goods/328_694d391a91da4.webp", + "name_cn": "卡比龙/Cigaronne 幻影 银色", + "name_jp": "シガローネ・ファントム・シルバー", + "description": "", + "price_jpy": 1100, + "category": "Cigaronne/卡比龙", + "price_cny": 52.8 + }, + { + "external_id": "T580", + "image_url": "https://ribenyan.com/data/images/goods/580_68f0fad2d91d1.webp", + "name_cn": "卡比龙/Cigaronne Super Slims 薄荷", + "name_jp": "シガローネ スーパースリム・メンソール", + "description": "", + "price_jpy": 780, + "category": "Cigaronne/卡比龙", + "price_cny": 37.44 + }, + { + "external_id": "2538", + "image_url": "https://ribenyan.com/data/images/goods/342_694e428272a6d.webp", + "name_cn": "卡比龙/Cigaronne Tattoo 樱桃", + "name_jp": "シガローネ・タトゥー・チェリー", + "description": "", + "price_jpy": 800, + "category": "Cigaronne/卡比龙", + "price_cny": 38.4 + }, + { + "external_id": "2540", + "image_url": "https://ribenyan.com/data/images/goods/343_694e42905691d.webp", + "name_cn": "卡比龙/Cigaronne Tattoo 香草", + "name_jp": "シガローネ・タトゥー・バニラ", + "description": "", + "price_jpy": 800, + "category": "Cigaronne/卡比龙", + "price_cny": 38.4 + }, + { + "external_id": "2608", + "image_url": "https://ribenyan.com/data/images/goods/323_694d38ee7ec28.webp", + "name_cn": "卡比龙/Cigaronne 极致细 黑色", + "name_jp": "シガローネ・ウルトラスリム・ブラック", + "description": "", + "price_jpy": 700, + "category": "Cigaronne/卡比龙", + "price_cny": 33.6 + }, + { + "external_id": "4329", + "image_url": "https://ribenyan.com/data/images/goods/707_694d39c7e6389.webp", + "name_cn": "Djarum Black 8", + "name_jp": "ジャルム・ブラック・8", + "description": "", + "price_jpy": 280, + "category": "DJARUM", + "price_cny": 13.44 + }, + { + "external_id": "4517", + "image_url": "https://ribenyan.com/data/images/goods/709_694d39f59514a.webp", + "name_cn": "Djarum Black Mint Tea 8", + "name_jp": "ジャルム・ブラック・ ミントティー8", + "description": "", + "price_jpy": 280, + "category": "DJARUM", + "price_cny": 13.44 + }, + { + "external_id": "4330", + "image_url": "https://ribenyan.com/data/images/goods/708_694d39dd469a6.webp", + "name_cn": "Djarum Black Jasmine Tea 8", + "name_jp": "ジャルム・ブラック・ ジャスミンティー・8", + "description": "", + "price_jpy": 280, + "category": "DJARUM", + "price_cny": 13.44 + }, + { + "external_id": "4331", + "image_url": "https://ribenyan.com/data/images/goods/710_694d3a1844f23.webp", + "name_cn": "Djarum Black Menthol 8", + "name_jp": "ジャルム・ブラック・ メンソール・8", + "description": "", + "price_jpy": 280, + "category": "DJARUM", + "price_cny": 13.44 + }, + { + "external_id": "2793", + "image_url": "https://ribenyan.com/data/images/goods/330_694d399ac1a20.webp", + "name_cn": "Djarum Super", + "name_jp": "ジャルム・スーパー・16", + "description": "", + "price_jpy": 510, + "category": "DJARUM", + "price_cny": 24.48 + }, + { + "external_id": "2114", + "image_url": "https://ribenyan.com/data/images/goods/297_690d69097a923.webp", + "name_cn": "福尔特/Forte 16支 Extra Light", + "name_jp": "フォルテ16・エクストラライト", + "description": "", + "price_jpy": 390, + "category": "Forte/福尔特", + "price_cny": 18.72 + }, + { + "external_id": "3733", + "image_url": "https://ribenyan.com/data/images/goods/302_690d6831e2317.webp", + "name_cn": "福尔特/Forte Extra Light", + "name_jp": "フォルテ・エクストラライト", + "description": "", + "price_jpy": 460, + "category": "Forte/福尔特", + "price_cny": 22.08 + }, + { + "external_id": "2094", + "image_url": "https://ribenyan.com/data/images/goods/303_690d68bccf060.webp", + "name_cn": "福尔特/Forte Original", + "name_jp": "フォルテ・オリジナル", + "description": "", + "price_jpy": 460, + "category": "Forte/福尔特", + "price_cny": 22.08 + }, + { + "external_id": "3732", + "image_url": "https://ribenyan.com/data/images/goods/295_690d687e79fb1.webp", + "name_cn": "福尔特/Forte Extra Light 薄荷", + "name_jp": "フォルテ・エクストラライトメンソール", + "description": "", + "price_jpy": 460, + "category": "Forte/福尔特", + "price_cny": 22.08 + }, + { + "external_id": "2199", + "image_url": "https://ribenyan.com/data/images/goods/296_690c4b491267c.webp", + "name_cn": "福尔特/Forte 薄荷", + "name_jp": "フォルテ・メンソール", + "description": "", + "price_jpy": 460, + "category": "Forte/福尔特", + "price_cny": 22.08 + }, + { + "external_id": "3980", + "image_url": "https://ribenyan.com/data/images/goods/305_690c622818bbc.webp", + "name_cn": "福尔特/Forte 16支 薄荷", + "name_jp": "フォルテ16・メンソール", + "description": "", + "price_jpy": 390, + "category": "Forte/福尔特", + "price_cny": 18.72 + }, + { + "external_id": "3979", + "image_url": "https://ribenyan.com/data/images/goods/298_690d693fdac0f.webp", + "name_cn": "福尔特/Forte 16支 Original", + "name_jp": "フォルテ16・オリジナル", + "description": "", + "price_jpy": 390, + "category": "Forte/福尔特", + "price_cny": 18.72 + }, + { + "external_id": "3981", + "image_url": "https://ribenyan.com/data/images/goods/299_690d69732638d.webp", + "name_cn": "福尔特/Forte 16支 Lights", + "name_jp": "フォルテ16・ライト", + "description": "", + "price_jpy": 390, + "category": "Forte/福尔特", + "price_cny": 18.72 + }, + { + "external_id": "2117", + "image_url": "https://ribenyan.com/data/images/goods/304_690c620e1a4ff.webp", + "name_cn": "福尔特/Forte 16支 Extra Light 薄荷", + "name_jp": "フォルテ16・エクストラライトメンソール", + "description": "", + "price_jpy": 390, + "category": "Forte/福尔特", + "price_cny": 18.72 + }, + { + "external_id": "2095", + "image_url": "https://ribenyan.com/data/images/goods/306_690d68edd4231.webp", + "name_cn": "福尔特/Forte Lights", + "name_jp": "フォルテ・ライト", + "description": "", + "price_jpy": 460, + "category": "Forte/福尔特", + "price_cny": 22.08 + }, + { + "external_id": "2627", + "image_url": "https://ribenyan.com/data/images/goods/320_694d38500f5f3.webp", + "name_cn": "盐仓/GudangGaram Surya 36支", + "name_jp": "ガラム・スーリア・缶(36)", + "description": "每条10罐", + "price_jpy": 1400, + "category": "GudangGaram/盐仓", + "price_cny": 67.2 + }, + { + "external_id": "2631", + "image_url": "https://ribenyan.com/data/images/goods/321_694d38624d02e.webp", + "name_cn": "盐仓/GudangGaram Surya Mild 16支", + "name_jp": "ガラム・スーリア・マイルド(16)", + "description": "", + "price_jpy": 530, + "category": "GudangGaram/盐仓", + "price_cny": 25.44 + }, + { + "external_id": "3064", + "image_url": "https://ribenyan.com/data/images/goods/319_694d382fd095b.webp", + "name_cn": "盐仓/GudangGaram Signature 薄荷", + "name_jp": "ガラム・シグネチャー・メンソール", + "description": "", + "price_jpy": 580, + "category": "GudangGaram/盐仓", + "price_cny": 27.84 + }, + { + "external_id": "2941", + "image_url": "https://ribenyan.com/data/images/goods/322_694d389c6eda5.webp", + "name_cn": "盐仓/GudangGaram Garam Nusantara Mild 12支", + "name_jp": "ガラム・ヌサンタラ・マイルド12", + "description": "", + "price_jpy": 380, + "category": "GudangGaram/盐仓", + "price_cny": 18.24 + }, + { + "external_id": "3063", + "image_url": "https://ribenyan.com/data/images/goods/318_694d382028513.webp", + "name_cn": "盐仓/GudangGaram Signature Mild", + "name_jp": "ガラム・シグネチャー・マイルド", + "description": "", + "price_jpy": 580, + "category": "GudangGaram/盐仓", + "price_cny": 27.84 + }, + { + "external_id": "2633", + "image_url": "https://ribenyan.com/data/images/goods/325_694d38dd768f9.webp", + "name_cn": "盐仓/GudangGaram 薄荷 16支", + "name_jp": "ガラム・メンソール(16)", + "description": "", + "price_jpy": 530, + "category": "GudangGaram/盐仓", + "price_cny": 25.44 + }, + { + "external_id": "2942", + "image_url": "https://ribenyan.com/data/images/goods/324_694d38b5b6a81.webp", + "name_cn": "盐仓/GudangGaram Nusantara 薄荷 12支", + "name_jp": "ガラム・ヌサンタラ・メンソール12", + "description": "", + "price_jpy": 380, + "category": "GudangGaram/盐仓", + "price_cny": 18.24 + }, + { + "external_id": "2399", + "image_url": "https://ribenyan.com/data/images/goods/289_690d66d99a2b9.webp", + "name_cn": "HARVEST King Size Cherry", + "name_jp": "ハーベスト・キングサイズ・チェリー", + "description": "", + "price_jpy": 480, + "category": "HARVEST", + "price_cny": 23.04 + }, + { + "external_id": "2410", + "image_url": "https://ribenyan.com/data/images/goods/290_690d66fab6656.webp", + "name_cn": "HARVEST King Size Vanilla", + "name_jp": "ハーベスト・キングサイズ・バニラ", + "description": "", + "price_jpy": 480, + "category": "HARVEST", + "price_cny": 23.04 + }, + { + "external_id": "2456", + "image_url": "https://ribenyan.com/data/images/goods/291_690d677abd3fc.webp", + "name_cn": "HARVEST 超细 Double Cherry", + "name_jp": "ハーベスト・スーパースリム・ダブルチェリー", + "description": "", + "price_jpy": 540, + "category": "HARVEST", + "price_cny": 25.92 + }, + { + "external_id": "2290", + "image_url": "https://ribenyan.com/data/images/goods/294_690d67ede036e.webp", + "name_cn": "HARVEST 超细 薄荷", + "name_jp": "ハーベスト・スーパースリム・メンソール", + "description": "", + "price_jpy": 540, + "category": "HARVEST", + "price_cny": 25.92 + }, + { + "external_id": "3817", + "image_url": "https://ribenyan.com/data/images/goods/292_690d6742f34f1.webp", + "name_cn": "HARVEST 超细 经典", + "name_jp": "ハーベスト・スーパースリム・クラシック", + "description": "", + "price_jpy": 540, + "category": "HARVEST", + "price_cny": 25.92 + }, + { + "external_id": "2457", + "image_url": "https://ribenyan.com/data/images/goods/293_690d67c757a38.webp", + "name_cn": "HARVEST 超细 Double Vanilla", + "name_jp": "ハーベスト・スーパースリム・ダブルバニラ", + "description": "", + "price_jpy": 540, + "category": "HARVEST", + "price_cny": 25.92 + }, + { + "external_id": "2739", + "image_url": "https://ribenyan.com/data/images/goods/235_690c695894f32.webp", + "name_cn": "J.P.S. KS 硬盒", + "name_jp": "ジェー・ピー・エス・KS・ボックス", + "description": "", + "price_jpy": 560, + "category": "J.S.P.", + "price_cny": 26.88 + }, + { + "external_id": "3052", + "image_url": "https://ribenyan.com/data/images/goods/331_694d3a30491a0.webp", + "name_cn": "小丑/Joker Chaos 13mg", + "name_jp": "ジョーカー・カオス", + "description": "", + "price_jpy": 630, + "category": "Joker/小丑", + "price_cny": 30.24 + }, + { + "external_id": "2127", + "image_url": "https://ribenyan.com/data/images/goods/332_694d3a696d679.webp", + "name_cn": "小丑/Joker Mystic 7mg", + "name_jp": "ジョーカー・ミスティック", + "description": "", + "price_jpy": 630, + "category": "Joker/小丑", + "price_cny": 30.24 + }, + { + "external_id": "2699", + "image_url": "https://ribenyan.com/data/images/goods/172_690c5e6d49deb.webp", + "name_cn": "健牌/箭牌/Kent 6mg 长支 硬盒", + "name_jp": "ケント・6・100・ボックス", + "description": "", + "price_jpy": 520, + "category": "Kent/健牌/箭牌", + "price_cny": 24.96 + }, + { + "external_id": "4533", + "image_url": "https://ribenyan.com/data/images/goods/175_690c5eb372c27.webp", + "name_cn": "健牌/箭牌/Kent S系列 Spark 1mg 长支 硬盒", + "name_jp": "ケント・エス・シリーズ・スパーク・1・100・ボックス", + "description": "", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "4534", + "image_url": "https://ribenyan.com/data/images/goods/176_690c5f0855612.webp", + "name_cn": "健牌/箭牌/Kent S系列 Spark 5mg 长支 硬盒", + "name_jp": "ケント・エス・シリーズ・スパーク・5・100・ボックス", + "description": "", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "2190", + "image_url": "https://ribenyan.com/data/images/goods/178_690c5f4e4d51d.webp", + "name_cn": "健牌/箭牌/Kent S系列 Spark 薄荷 5mg 长支 硬盒", + "name_jp": "ケント・エス・シリーズ・スパーク・ミント・5・100・ボックス", + "description": "", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "2311", + "image_url": "https://ribenyan.com/data/images/goods/179_690c5f6ac26cc.webp", + "name_cn": "健牌/箭牌/Kent S系列 Spark Mellow 1mg 长支 硬盒", + "name_jp": "ケント・エス・シリーズ・スパーク・メロー・1・100・ボックス", + "description": "", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "2705", + "image_url": "https://ribenyan.com/data/images/goods/169_690c5a04ecaeb.webp", + "name_cn": "健牌/箭牌/Kent 1mg KS 硬盒", + "name_jp": "ケント・1・KS・ボックス", + "description": "", + "price_jpy": 520, + "category": "Kent/健牌/箭牌", + "price_cny": 24.96 + }, + { + "external_id": "4524", + "image_url": "https://ribenyan.com/data/images/goods/182_690c5fcee3853.webp", + "name_cn": "健牌/箭牌/Kent S系列 3mg 长支 硬盒", + "name_jp": "ケント・エス・シリーズ・3・100・ボックス", + "description": "", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "4375", + "image_url": "https://ribenyan.com/data/images/goods/181_690c5fadaec2b.webp", + "name_cn": "健牌/箭牌/Kent S系列 1mg 长支 硬盒", + "name_jp": "ケント・エス・シリーズ・1・100・ボックス", + "description": "", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "3242", + "image_url": "https://ribenyan.com/data/images/goods/173_690c5e36b0e29.webp", + "name_cn": "健牌/箭牌/Kent 3mg 长支 硬盒", + "name_jp": "ケント・3・100・ボックス", + "description": "", + "price_jpy": 520, + "category": "Kent/健牌/箭牌", + "price_cny": 24.96 + }, + { + "external_id": "4180", + "image_url": "https://ribenyan.com/data/images/goods/174_690c5e9129a7a.webp", + "name_cn": "健牌/箭牌/Kent I-Boost 1mg 长支 硬盒", + "name_jp": "ケント・アイ・ブースト・1・100・ボックス", + "description": "", + "price_jpy": 520, + "category": "Kent/健牌/箭牌", + "price_cny": 24.96 + }, + { + "external_id": "4376", + "image_url": "https://ribenyan.com/data/images/goods/180_690c5f8627e24.webp", + "name_cn": "健牌/箭牌/Kent S系列 薄荷 1mg 长支 硬盒", + "name_jp": "ケント・エス・シリーズ・メンソール・1・100・ボックス", + "description": "", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "2746", + "image_url": "https://ribenyan.com/data/images/goods/170_690c5e03eafa9.webp", + "name_cn": "健牌/箭牌/Kent 1mg 长支 硬盒", + "name_jp": "ケント・1・100・ボックス", + "description": "", + "price_jpy": 520, + "category": "Kent/健牌/箭牌", + "price_cny": 24.96 + }, + { + "external_id": "2088", + "image_url": "https://ribenyan.com/data/images/goods/177_690c5f273fc66.webp", + "name_cn": "健牌/箭牌/Kent S系列 Spark 薄荷 1mg 长支 硬盒", + "name_jp": "ケント・エス・シリーズ・スパーク・ミント・1・100・ボックス", + "description": "", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "3152", + "image_url": "https://ribenyan.com/data/images/goods/195_690c623672b6f.webp", + "name_cn": "KOOL Minty系列 Minty Berry 5mg", + "name_jp": "クール・ミンティ・シリーズ・ミンティベリー・5", + "description": "", + "price_jpy": 500, + "category": "KOOL", + "price_cny": 24.0 + }, + { + "external_id": "3154", + "image_url": "https://ribenyan.com/data/images/goods/194_690c61fa9585c.webp", + "name_cn": "KOOL Minty系列 Minty Boost 8mg", + "name_jp": "クール・ミンティ・シリーズ・ミンティブースト・8", + "description": "", + "price_jpy": 500, + "category": "KOOL", + "price_cny": 24.0 + }, + { + "external_id": "2619", + "image_url": "https://ribenyan.com/data/images/goods/157_690c60c65736c.webp", + "name_cn": "KOOL FK 硬盒", + "name_jp": "クール・FK・ボックス", + "description": "", + "price_jpy": 580, + "category": "KOOL", + "price_cny": 27.84 + }, + { + "external_id": "2213", + "image_url": "https://ribenyan.com/data/images/goods/281_690d6436e4038.webp", + "name_cn": "KOOL Looped Sangria", + "name_jp": "クール・ループド・サングリア", + "description": "", + "price_jpy": 500, + "category": "KOOL", + "price_cny": 24.0 + }, + { + "external_id": "3273", + "image_url": "https://ribenyan.com/data/images/goods/191_690c61a14ff17.webp", + "name_cn": "KOOL Boost Fresh 8mg 硬盒", + "name_jp": "クール・ブースト・フレッシュ・8・ボックス", + "description": "", + "price_jpy": 580, + "category": "KOOL", + "price_cny": 27.84 + }, + { + "external_id": "3312", + "image_url": "https://ribenyan.com/data/images/goods/190_690c6184af636.webp", + "name_cn": "KOOL Boost Fresh 5mg 硬盒", + "name_jp": "クール・ブースト・フレッシュ・5・ボックス", + "description": "", + "price_jpy": 580, + "category": "KOOL", + "price_cny": 27.84 + }, + { + "external_id": "3786", + "image_url": "https://ribenyan.com/data/images/goods/282_690d645c621a4.webp", + "name_cn": "KOOL Looped Plus", + "name_jp": "クール・ループド・プラス", + "description": "", + "price_jpy": 500, + "category": "KOOL", + "price_cny": 24.0 + }, + { + "external_id": "3785", + "image_url": "https://ribenyan.com/data/images/goods/280_690d63e5d9a98.webp", + "name_cn": "KOOL Looped", + "name_jp": "クール・ループド", + "description": "", + "price_jpy": 500, + "category": "KOOL", + "price_cny": 24.0 + }, + { + "external_id": "2756", + "image_url": "https://ribenyan.com/data/images/goods/192_690c61bd777d6.webp", + "name_cn": "KOOL Milds FK 硬盒", + "name_jp": "クール・マイルド・FK・ボックス", + "description": "", + "price_jpy": 580, + "category": "KOOL", + "price_cny": 27.84 + }, + { + "external_id": "2948", + "image_url": "https://ribenyan.com/data/images/goods/196_690c62511396b.webp", + "name_cn": "KOOL 淡味 硬盒", + "name_jp": "クール・ライト・ボックス", + "description": "", + "price_jpy": 580, + "category": "KOOL", + "price_cny": 27.84 + }, + { + "external_id": "2617", + "image_url": "https://ribenyan.com/data/images/goods/355_694d37b04be2e.webp", + "name_cn": "L.A. Bold", + "name_jp": "L.A.・ボールド", + "description": "", + "price_jpy": 350, + "category": "L.A.", + "price_cny": 16.8 + }, + { + "external_id": "2616", + "image_url": "https://ribenyan.com/data/images/goods/353_694d379e2b028.webp", + "name_cn": "L.A. Ice Purple Boost", + "name_jp": "L・A・アイスパープル・ブースト", + "description": "", + "price_jpy": 500, + "category": "L.A.", + "price_cny": 24.0 + }, + { + "external_id": "2615", + "image_url": "https://ribenyan.com/data/images/goods/354_694d353a0aca1.webp", + "name_cn": "L.A. Ice", + "name_jp": "L・A・アイス", + "description": "", + "price_jpy": 500, + "category": "L.A.", + "price_cny": 24.0 + }, + { + "external_id": "2620", + "image_url": "https://ribenyan.com/data/images/goods/150_690c4b38f35f1.webp", + "name_cn": "云雀/Lark 12mg 长支 硬盒", + "name_jp": "ラーク・100ボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "2563", + "image_url": "https://ribenyan.com/data/images/goods/148_690c4b093e643.webp", + "name_cn": "云雀/Lark 12mg KS 硬盒", + "name_jp": "ラークKSボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "2542", + "image_url": "https://ribenyan.com/data/images/goods/160_690c5879e38aa.webp", + "name_cn": "云雀/Lark Super 5mg KS 硬盒", + "name_jp": "ラーク・スーパー・5mgKSボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "4142", + "image_url": "https://ribenyan.com/data/images/goods/166_690c59a0eb03b.webp", + "name_cn": "云雀/Lark Hybrid KS 硬盒", + "name_jp": "ラーク・ハイブリッドKSボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "3310", + "image_url": "https://ribenyan.com/data/images/goods/158_690c57dbad7a5.webp", + "name_cn": "云雀/Lark Classic Mild KS 硬盒", + "name_jp": "ラーク・クラシック・マイルドKSボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "2524", + "image_url": "https://ribenyan.com/data/images/goods/167_690c59be29d06.webp", + "name_cn": "云雀/Lark Mild KS 硬盒", + "name_jp": "ラーク・マイルドKSボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "3031", + "image_url": "https://ribenyan.com/data/images/goods/152_690c57732c4b3.webp", + "name_cn": "云雀/Lark Ultra 1mg 长支 硬盒", + "name_jp": "ラーク・ウルトラ・1mg100ボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "3219", + "image_url": "https://ribenyan.com/data/images/goods/154_690c57b82a18e.webp", + "name_cn": "云雀/Lark Extra 3mg 长支 硬盒", + "name_jp": "ラーク・エクストラ・3mg100ボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "2403", + "image_url": "https://ribenyan.com/data/images/goods/164_690c57f433929.webp", + "name_cn": "云雀/Lark Select 薄荷 1mg 长支 硬盒", + "name_jp": "ラーク・セレクト・メンソール・1・100ボックス", + "description": "", + "price_jpy": 500, + "category": "Lark/云雀", + "price_cny": 24.0 + }, + { + "external_id": "2404", + "image_url": "https://ribenyan.com/data/images/goods/165_690c595f4e2f2.webp", + "name_cn": "云雀/Lark Select 薄荷 5mg 长支 硬盒", + "name_jp": "ラーク・セレクト・メンソール・5・100ボックス", + "description": "", + "price_jpy": 500, + "category": "Lark/云雀", + "price_cny": 24.0 + }, + { + "external_id": "2411", + "image_url": "https://ribenyan.com/data/images/goods/168_690c59e277d78.webp", + "name_cn": "云雀/Lark Mild 长支 硬盒", + "name_jp": "ラーク・マイルド100ボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "3195", + "image_url": "https://ribenyan.com/data/images/goods/153_690c5796c2185.webp", + "name_cn": "云雀/Lark Extra 3mg KS 硬盒", + "name_jp": "ラーク・エクストラ・3mgKSボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "3218", + "image_url": "https://ribenyan.com/data/images/goods/159_690c585782b6f.webp", + "name_cn": "云雀/Lark Super Mild 长支 硬盒", + "name_jp": "ラーク・スーパー・マイルド100ボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "2888", + "image_url": "https://ribenyan.com/data/images/goods/151_690c57556ded0.webp", + "name_cn": "云雀/Lark Ultra 1mg KS 硬盒", + "name_jp": "ラーク・ウルトラ・1mgKSボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "2402", + "image_url": "https://ribenyan.com/data/images/goods/161_690c58f62e27c.webp", + "name_cn": "云雀/Lark Select 1mg 长支 硬盒", + "name_jp": "ラーク・セレクト・1・100ボックス", + "description": "", + "price_jpy": 500, + "category": "Lark/云雀", + "price_cny": 24.0 + }, + { + "external_id": "2625", + "image_url": "https://ribenyan.com/data/images/goods/162_690c589499213.webp", + "name_cn": "云雀/Lark Super 5mg 长支 硬盒", + "name_jp": "ラーク・スーパー・5mg100ボックス", + "description": "", + "price_jpy": 540, + "category": "Lark/云雀", + "price_cny": 25.92 + }, + { + "external_id": "2405", + "image_url": "https://ribenyan.com/data/images/goods/163_690c5927403c6.webp", + "name_cn": "云雀/Lark Select Purple 1mg 长支 硬盒", + "name_jp": "ラーク・セレクト・パープル・1・100ボックス", + "description": "", + "price_jpy": 500, + "category": "Lark/云雀", + "price_cny": 24.0 + }, + { + "external_id": "2380", + "image_url": "https://ribenyan.com/data/images/goods/211_690c647277595.webp", + "name_cn": "好彩/LuckyStrike 红 11mg 软盒", + "name_jp": "ラッキー・ストライクFK", + "description": "", + "price_jpy": 620, + "category": "LuckyStrike/好彩", + "price_cny": 29.76 + }, + { + "external_id": "2525", + "image_url": "https://ribenyan.com/data/images/goods/223_690c67ad208d1.webp", + "name_cn": "好彩/LuckyStrike 红 11mg 硬盒", + "name_jp": "ラッキー・ストライク・ボックス", + "description": "", + "price_jpy": 620, + "category": "LuckyStrike/好彩", + "price_cny": 29.76 + }, + { + "external_id": "3789", + "image_url": "https://ribenyan.com/data/images/goods/313_690c8793b118e.webp", + "name_cn": "好彩/LuckyStrike 过滤嘴 雪茄型 细支 淡味", + "name_jp": "ラッキー・ストライク・フィルター・シガリロ・スリム・ライト", + "description": "", + "price_jpy": 470, + "category": "LuckyStrike/好彩", + "price_cny": 22.56 + }, + { + "external_id": "3062", + "image_url": "https://ribenyan.com/data/images/goods/224_690c67cc26576.webp", + "name_cn": "好彩/LuckyStrike 淡味 6mg 硬盒", + "name_jp": "ラッキー・ストライク・ライトボックス", + "description": "", + "price_jpy": 620, + "category": "LuckyStrike/好彩", + "price_cny": 29.76 + }, + { + "external_id": "2264", + "image_url": "https://ribenyan.com/data/images/goods/217_690c670cabb60.webp", + "name_cn": "好彩/LuckyStrike Expert Cut 14mg", + "name_jp": "ラッキー・ストライク・エキスパートカット・14", + "description": "", + "price_jpy": 470, + "category": "LuckyStrike/好彩", + "price_cny": 22.56 + }, + { + "external_id": "3057", + "image_url": "https://ribenyan.com/data/images/goods/222_690c6791de531.webp", + "name_cn": "好彩/LuckyStrike 黑色系列 Chillberry 8mg", + "name_jp": "ラッキー・ストライク・ブラック・シリーズ・チルベリー・8", + "description": "", + "price_jpy": 470, + "category": "LuckyStrike/好彩", + "price_cny": 22.56 + }, + { + "external_id": "3058", + "image_url": "https://ribenyan.com/data/images/goods/221_690c67755286e.webp", + "name_cn": "好彩/LuckyStrike 黑色系列 Chillberry 5mg", + "name_jp": "ラッキー・ストライク・ブラック・シリーズ・チルベリー・5", + "description": "", + "price_jpy": 470, + "category": "LuckyStrike/好彩", + "price_cny": 22.56 + }, + { + "external_id": "3044", + "image_url": "https://ribenyan.com/data/images/goods/212_690c648f54383.webp", + "name_cn": "好彩/LuckyStrike Expert Cut 1mg 长支 硬盒", + "name_jp": "ラッキー・ストライク・エキスパートカット・1・100", + "description": "", + "price_jpy": 470, + "category": "LuckyStrike/好彩", + "price_cny": 22.56 + }, + { + "external_id": "3787", + "image_url": "https://ribenyan.com/data/images/goods/218_690c6726da20b.webp", + "name_cn": "好彩/LuckyStrike 黑色系列 薄荷 5mg", + "name_jp": "ラッキー・ストライク・ブラック・シリーズ・メンソール・5", + "description": "", + "price_jpy": 470, + "category": "LuckyStrike/好彩", + "price_cny": 22.56 + }, + { + "external_id": "2975", + "image_url": "https://ribenyan.com/data/images/goods/965_69ce5b941b6d8.webp", + "name_cn": "好彩/LuckyStrike 10 Box", + "name_jp": "ラッキーズ・10・ボックス", + "description": "", + "price_jpy": 450, + "category": "LuckyStrike/好彩", + "price_cny": 21.6 + }, + { + "external_id": "3043", + "image_url": "https://ribenyan.com/data/images/goods/220_690c675dcc444.webp", + "name_cn": "好彩/LuckyStrike 黑色系列 Chillberry 1mg 长支", + "name_jp": "ラッキー・ストライク・ブラック・シリーズ・チルベリー・1・100", + "description": "", + "price_jpy": 470, + "category": "LuckyStrike/好彩", + "price_cny": 22.56 + }, + { + "external_id": "3788", + "image_url": "https://ribenyan.com/data/images/goods/219_690c6744b9bdc.webp", + "name_cn": "好彩/LuckyStrike 黑色系列 薄荷 8mg", + "name_jp": "ラッキー・ストライク・ブラック・シリーズ・メンソール・8", + "description": "", + "price_jpy": 470, + "category": "LuckyStrike/好彩", + "price_cny": 22.56 + }, + { + "external_id": "2261", + "image_url": "https://ribenyan.com/data/images/goods/216_690c66b93b84e.webp", + "name_cn": "好彩/LuckyStrike Expert Cut 10mg", + "name_jp": "ラッキー・ストライク・エキスパートカット・10", + "description": "", + "price_jpy": 470, + "category": "LuckyStrike/好彩", + "price_cny": 22.56 + }, + { + "external_id": "2260", + "image_url": "https://ribenyan.com/data/images/goods/215_690c64e580e4a.webp", + "name_cn": "好彩/LuckyStrike Expert Cut 6mg", + "name_jp": "ラッキー・ストライク・エキスパートカット・6", + "description": "", + "price_jpy": 470, + "category": "LuckyStrike/好彩", + "price_cny": 22.56 + }, + { + "external_id": "2267", + "image_url": "https://ribenyan.com/data/images/goods/214_690c65afe4b2d.webp", + "name_cn": "好彩/LuckyStrike Expert Cut 8mg", + "name_jp": "ラッキー・ストライク・エキスパートカット・8", + "description": "", + "price_jpy": 470, + "category": "LuckyStrike/好彩", + "price_cny": 22.56 + }, + { + "external_id": "2974", + "image_url": "https://ribenyan.com/data/images/goods/964_69ce5b2e2e62e.webp", + "name_cn": "好彩/LuckyStrike 6 Box", + "name_jp": "ラッキーズ・6・ボックス", + "description": "", + "price_jpy": 450, + "category": "LuckyStrike/好彩", + "price_cny": 21.6 + }, + { + "external_id": "2266", + "image_url": "https://ribenyan.com/data/images/goods/213_690c64c50c542.webp", + "name_cn": "好彩/LuckyStrike Expert Cut 3mg", + "name_jp": "ラッキー・ストライク・エキスパートカット・3", + "description": "", + "price_jpy": 470, + "category": "LuckyStrike/好彩", + "price_cny": 22.56 + }, + { + "external_id": "2927", + "image_url": "https://ribenyan.com/data/images/goods/139_690c49a8bf7f1.webp", + "name_cn": "万宝路/Marlboro 金 硬盒", + "name_jp": "マールボロ・ゴールド・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "2923", + "image_url": "https://ribenyan.com/data/images/goods/144_690c4a5f9afd4.webp", + "name_cn": "万宝路/Marlboro 中醇 硬盒", + "name_jp": "マールボロ・ミディアム・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "2920", + "image_url": "https://ribenyan.com/data/images/goods/133_690c1f3649a00.webp", + "name_cn": "万宝路/Marlboro 红 软盒", + "name_jp": "マールボロ", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "2389", + "image_url": "https://ribenyan.com/data/images/goods/142_690c4a1381405.webp", + "name_cn": "万宝路/Marlboro 热带 激浪 8mg 硬盒", + "name_jp": "マールボロ・トロピカル・スプラッシュ・8・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "2986", + "image_url": "https://ribenyan.com/data/images/goods/140_690c49c9cc7af.webp", + "name_cn": "万宝路/Marlboro 金 长支 硬盒", + "name_jp": "マールボロ・ゴールド・100ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "2921", + "image_url": "https://ribenyan.com/data/images/goods/134_690c3ee246d92.webp", + "name_cn": "万宝路/Marlboro 红 硬盒", + "name_jp": "マールボロ・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "4349", + "image_url": "https://ribenyan.com/data/images/goods/141_690c49f198a6b.webp", + "name_cn": "万宝路/Marlboro 双爆珠 5mg 硬盒", + "name_jp": "マールボロ・ダブル・バースト・5・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "4460", + "image_url": "https://ribenyan.com/data/images/goods/138_690c498941245.webp", + "name_cn": "万宝路/Marlboro 清透 混合 3KSB", + "name_jp": "マールボロ・クリア・ハイブリッド3KSB", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "2925", + "image_url": "https://ribenyan.com/data/images/goods/149_690c4adc88afc.webp", + "name_cn": "万宝路/Marlboro 薄荷 12mg 硬盒", + "name_jp": "マールボロ・メンソール・12・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "4145", + "image_url": "https://ribenyan.com/data/images/goods/137_690c3f52a8783.webp", + "name_cn": "万宝路/Marlboro 冰爆 超劲 8mg 硬盒", + "name_jp": "マールボロ・アイス・ブラスト・メガ・8・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "2930", + "image_url": "https://ribenyan.com/data/images/goods/147_690c4ab99e5d6.webp", + "name_cn": "万宝路/Marlboro 薄荷 8mg 长支 硬盒", + "name_jp": "マールボロ・メンソール・8・100s・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "3318", + "image_url": "https://ribenyan.com/data/images/goods/143_690c4a33e7f25.webp", + "name_cn": "万宝路/Marlboro 黑色 薄荷 8mg 硬盒", + "name_jp": "マールボロ・ブラック・メンソール・8・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "2929", + "image_url": "https://ribenyan.com/data/images/goods/146_690c4a9c55187.webp", + "name_cn": "万宝路/Marlboro 薄荷 8mg 硬盒", + "name_jp": "マールボロ・メンソール・8・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "2944", + "image_url": "https://ribenyan.com/data/images/goods/145_690c4a7f89360.webp", + "name_cn": "万宝路/Marlboro 薄荷 4mg 硬盒", + "name_jp": "マールボロ・メンソール・4・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "4280", + "image_url": "https://ribenyan.com/data/images/goods/135_690c3f0b80674.webp", + "name_cn": "万宝路/Marlboro 冰爆 超劲 1mg 硬盒", + "name_jp": "マールボロ・アイス・ブラスト・メガ・1・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "4281", + "image_url": "https://ribenyan.com/data/images/goods/136_690c3f3067516.webp", + "name_cn": "万宝路/Marlboro 冰爆 超劲 5mg 硬盒", + "name_jp": "マールボロ・アイス・ブラスト・メガ・5・ボックス", + "description": "", + "price_jpy": 620, + "category": "Marlboro/万宝路", + "price_cny": 29.76 + }, + { + "external_id": "2097", + "image_url": "https://ribenyan.com/data/images/goods/316_694d37f5762a3.webp", + "name_cn": "OSMO 10mg", + "name_jp": "オズモ・10", + "description": "", + "price_jpy": 330, + "category": "OSMO", + "price_cny": 15.84 + }, + { + "external_id": "2098", + "image_url": "https://ribenyan.com/data/images/goods/317_694d38065fe2e.webp", + "name_cn": "OSMO 薄荷 6mg", + "name_jp": "オズモ・メンソール・6", + "description": "", + "price_jpy": 330, + "category": "OSMO", + "price_cny": 15.84 + }, + { + "external_id": "2453", + "image_url": "https://ribenyan.com/data/images/goods/183_690c600c91ac6.webp", + "name_cn": "百乐门/Parliament 9mg 长支 硬盒", + "name_jp": "パーラメント100ボックス", + "description": "", + "price_jpy": 620, + "category": "Parliament/百乐门", + "price_cny": 29.76 + }, + { + "external_id": "2560", + "image_url": "https://ribenyan.com/data/images/goods/184_690c5fec87c56.webp", + "name_cn": "百乐门/Parliament 9mg KS 硬盒", + "name_jp": "パーラメントKSボックス", + "description": "", + "price_jpy": 580, + "category": "Parliament/百乐门", + "price_cny": 27.84 + }, + { + "external_id": "2554", + "image_url": "https://ribenyan.com/data/images/goods/188_690c60896bfa2.webp", + "name_cn": "百乐门/Parliament 淡味 6mg 长支 硬盒", + "name_jp": "パーラメント・ライト100ボックス", + "description": "", + "price_jpy": 620, + "category": "Parliament/百乐门", + "price_cny": 29.76 + }, + { + "external_id": "2643", + "image_url": "https://ribenyan.com/data/images/goods/187_690c606960afb.webp", + "name_cn": "百乐门/Parliament 淡味 6mg KS 硬盒", + "name_jp": "パーラメント・ライトKSボックス", + "description": "", + "price_jpy": 580, + "category": "Parliament/百乐门", + "price_cny": 27.84 + }, + { + "external_id": "2803", + "image_url": "https://ribenyan.com/data/images/goods/186_690c6044acc83.webp", + "name_cn": "百乐门/Parliament 超淡 3mg 长支 硬盒", + "name_jp": "パーラメント・エクストラ・ライト・100ボックス", + "description": "", + "price_jpy": 620, + "category": "Parliament/百乐门", + "price_cny": 29.76 + }, + { + "external_id": "2865", + "image_url": "https://ribenyan.com/data/images/goods/189_690c60a38b165.webp", + "name_cn": "百乐门/Parliament One 1mg 长支 硬盒", + "name_jp": "パーラメント・ワン100ボックス", + "description": "", + "price_jpy": 620, + "category": "Parliament/百乐门", + "price_cny": 29.76 + }, + { + "external_id": "3055", + "image_url": "https://ribenyan.com/data/images/goods/338_694d3d4537624.webp", + "name_cn": "百乐/Peel Greek Yogurt (希腊酸奶)", + "name_jp": "ピール・ギリシャヨーグルト", + "description": "", + "price_jpy": 550, + "category": "Peel/百乐", + "price_cny": 26.4 + }, + { + "external_id": "2607", + "image_url": "https://ribenyan.com/data/images/goods/193_694d3b40dc07a.webp", + "name_cn": "百乐/Peel Red Wine (红酒)", + "name_jp": "ピール・レッドワイン", + "description": "", + "price_jpy": 550, + "category": "Peel/百乐", + "price_cny": 26.4 + }, + { + "external_id": "2339", + "image_url": "https://ribenyan.com/data/images/goods/340_694d3b27716e3.webp", + "name_cn": "百乐/Peel Grape (葡萄)", + "name_jp": "ピール・スプラッシュグレープ", + "description": "", + "price_jpy": 550, + "category": "Peel/百乐", + "price_cny": 26.4 + }, + { + "external_id": "3054", + "image_url": "https://ribenyan.com/data/images/goods/341_694d3b553d1db.webp", + "name_cn": "百乐/Peel Lemon Honey (柠檬蜂蜜)", + "name_jp": "ピール・レモンハニー", + "description": "", + "price_jpy": 550, + "category": "Peel/百乐", + "price_cny": 26.4 + }, + { + "external_id": "2197", + "image_url": "https://ribenyan.com/data/images/goods/339_694d3b0f2f72b.webp", + "name_cn": "百乐/Peel Orange (橙子)", + "name_jp": "ピール・スプラッシュオレンジ", + "description": "", + "price_jpy": 550, + "category": "Peel/百乐", + "price_cny": 26.4 + }, + { + "external_id": "3049", + "image_url": "https://ribenyan.com/data/images/goods/947_69be3917c8940.webp", + "name_cn": "百乐/Peel Watermelon (西瓜)", + "name_jp": "ピール・スイカ", + "description": "", + "price_jpy": 550, + "category": "Peel/百乐", + "price_cny": 26.4 + }, + { + "external_id": "2583", + "image_url": "https://ribenyan.com/data/images/goods/267_690c71298984a.webp", + "name_cn": "PEPE Organic 18支", + "name_jp": "ペペ・オーガニック・シガレット", + "description": "", + "price_jpy": 540, + "category": "PEPE", + "price_cny": 25.92 + }, + { + "external_id": "3040", + "image_url": "https://ribenyan.com/data/images/goods/268_690c71449cbdd.webp", + "name_cn": "PEPE Rich Green 18支", + "name_jp": "ペペ・リッチグリーン・シガレット", + "description": "", + "price_jpy": 500, + "category": "PEPE", + "price_cny": 24.0 + }, + { + "external_id": "3868", + "image_url": "https://ribenyan.com/data/images/goods/203_690c63354bbe7.webp", + "name_cn": "菲利普莫里斯/PhilipMorris 10mg KS 硬盒", + "name_jp": "フィリップモリス・10・KSボックス", + "description": "", + "price_jpy": 450, + "category": "PhilipMorris/菲利普莫里斯", + "price_cny": 21.6 + }, + { + "external_id": "3869", + "image_url": "https://ribenyan.com/data/images/goods/204_690c63526fb52.webp", + "name_cn": "菲利普莫里斯/PhilipMorris 14mg KS 硬盒", + "name_jp": "フィリップモリス・14・KSボックス", + "description": "", + "price_jpy": 450, + "category": "PhilipMorris/菲利普莫里斯", + "price_cny": 21.6 + }, + { + "external_id": "2111", + "image_url": "https://ribenyan.com/data/images/goods/207_690c63cfd65b5.webp", + "name_cn": "菲利普莫里斯/PhilipMorris Purple 8mg KS 硬盒", + "name_jp": "フィリップモリス・パープル・8・KSボックス", + "description": "", + "price_jpy": 450, + "category": "PhilipMorris/菲利普莫里斯", + "price_cny": 21.6 + }, + { + "external_id": "3871", + "image_url": "https://ribenyan.com/data/images/goods/210_690c6447ac7c9.webp", + "name_cn": "菲利普莫里斯/PhilipMorris 薄荷 8mg KS 硬盒", + "name_jp": "フィリップモリス・メンソール・8・KSボックス", + "description": "", + "price_jpy": 450, + "category": "PhilipMorris/菲利普莫里斯", + "price_cny": 21.6 + }, + { + "external_id": "4600", + "image_url": "https://ribenyan.com/data/images/goods/200_690c62de0cc49.webp", + "name_cn": "菲利普莫里斯/PhilipMorris 1mg 长支 硬盒", + "name_jp": "フィリップモリス・1・100sボックス", + "description": "", + "price_jpy": 450, + "category": "PhilipMorris/菲利普莫里斯", + "price_cny": 21.6 + }, + { + "external_id": "3867", + "image_url": "https://ribenyan.com/data/images/goods/202_690c6312d52e3.webp", + "name_cn": "菲利普莫里斯/PhilipMorris 6mg KS 硬盒", + "name_jp": "フィリップモリス・6・KSボックス", + "description": "", + "price_jpy": 450, + "category": "PhilipMorris/菲利普莫里斯", + "price_cny": 21.6 + }, + { + "external_id": "2842", + "image_url": "https://ribenyan.com/data/images/goods/205_690c63b6be6d2.webp", + "name_cn": "菲利普莫里斯/PhilipMorris Purple 5mg KS 硬盒", + "name_jp": "フィリップモリス・パープル・5・KSボックス", + "description": "", + "price_jpy": 450, + "category": "PhilipMorris/菲利普莫里斯", + "price_cny": 21.6 + }, + { + "external_id": "2843", + "image_url": "https://ribenyan.com/data/images/goods/208_690c63f42688c.webp", + "name_cn": "菲利普莫里斯/PhilipMorris 薄荷 1mg 长支 硬盒", + "name_jp": "フィリップモリス・メンソール・1・100sボックス", + "description": "", + "price_jpy": 450, + "category": "PhilipMorris/菲利普莫里斯", + "price_cny": 21.6 + }, + { + "external_id": "2638", + "image_url": "https://ribenyan.com/data/images/goods/155_690c62bd3fdbd.webp", + "name_cn": "菲利普莫里斯/PhilipMorris 1mg 长支 超细 硬盒 N", + "name_jp": "フィリップモリス・1・100sスーパースリムボックスN", + "description": "", + "price_jpy": 450, + "category": "PhilipMorris/菲利普莫里斯", + "price_cny": 21.6 + }, + { + "external_id": "2639", + "image_url": "https://ribenyan.com/data/images/goods/156_690c636e8fe51.webp", + "name_cn": "菲利普莫里斯/PhilipMorris Purple 1mg 长支 超细 N", + "name_jp": "フィリップモリス・パープル・1・100sスーパースリムボックスN", + "description": "", + "price_jpy": 450, + "category": "PhilipMorris/菲利普莫里斯", + "price_cny": 21.6 + }, + { + "external_id": "4595", + "image_url": "https://ribenyan.com/data/images/goods/201_690c62fa5a4e9.webp", + "name_cn": "菲利普莫里斯/PhilipMorris 3mg KS 硬盒", + "name_jp": "フィリップモリス・3・KSボックス", + "description": "", + "price_jpy": 450, + "category": "PhilipMorris/菲利普莫里斯", + "price_cny": 21.6 + }, + { + "external_id": "3870", + "image_url": "https://ribenyan.com/data/images/goods/209_690c640f89153.webp", + "name_cn": "菲利普莫里斯/PhilipMorris 薄荷 5mg KS 硬盒", + "name_jp": "フィリップモリス・メンソール・5・KSボックス", + "description": "", + "price_jpy": 450, + "category": "PhilipMorris/菲利普莫里斯", + "price_cny": 21.6 + }, + { + "external_id": "4293", + "image_url": "https://ribenyan.com/data/images/goods/248_690c6ec165b2d.webp", + "name_cn": "PUEBLO Natural 10 19支", + "name_jp": "プエブロ・ナチュラル・10", + "description": "", + "price_jpy": 520, + "category": "PUEBLO/普韦布洛", + "price_cny": 24.96 + }, + { + "external_id": "4294", + "image_url": "https://ribenyan.com/data/images/goods/247_690c6ea8e1355.webp", + "name_cn": "PUEBLO Natural 6 19支", + "name_jp": "プエブロ・ナチュラル・6", + "description": "", + "price_jpy": 520, + "category": "PUEBLO/普韦布洛", + "price_cny": 24.96 + }, + { + "external_id": "5200", + "image_url": "https://ribenyan.com/data/images/goods/937_69aa427d8654f.webp", + "name_cn": "Smokin Joes Full Flavor 13mg", + "name_jp": "スモーキンジョー フルフレーバー", + "description": "", + "price_jpy": 580, + "category": "Smokin Joes", + "price_cny": 27.84 + }, + { + "external_id": "5201", + "image_url": "https://ribenyan.com/data/images/goods/938_69aa436ca4084.webp", + "name_cn": "Smokin Joes Light 12mg", + "name_jp": "スモーキンジョー ライト", + "description": "", + "price_jpy": 580, + "category": "Smokin Joes", + "price_cny": 27.84 + }, + { + "external_id": "5203", + "image_url": "https://ribenyan.com/data/images/goods/940_69aa453c2164d.webp", + "name_cn": "Smokin Joes Menthol 12mg", + "name_jp": "スモーキンジョー メンソール", + "description": "", + "price_jpy": 580, + "category": "Smokin Joes", + "price_cny": 27.84 + }, + { + "external_id": "5202", + "image_url": "https://ribenyan.com/data/images/goods/939_69aa4480b8005.webp", + "name_cn": "Smokin Joes Ultra Light 11mg", + "name_jp": "スモーキンジョー ウルトラライト", + "description": "", + "price_jpy": 580, + "category": "Smokin Joes", + "price_cny": 27.84 + }, + { + "external_id": "2544", + "image_url": "https://ribenyan.com/data/images/goods/335_694d3aa36cdd0.webp", + "name_cn": "寿百年/Sobranie Black Russian 7mg", + "name_jp": "ソブラニー・ブラック・ロシアン", + "description": "", + "price_jpy": 1000, + "category": "Sobranie/寿百年", + "price_cny": 48.0 + }, + { + "external_id": "2543", + "image_url": "https://ribenyan.com/data/images/goods/334_694d3a8020d5d.webp", + "name_cn": "寿百年/Sobranie Cocktail 5mg", + "name_jp": "ソブラニー・カクテル", + "description": "", + "price_jpy": 1000, + "category": "Sobranie/寿百年", + "price_cny": 48.0 + }, + { + "external_id": "2609", + "image_url": "https://ribenyan.com/data/images/goods/337_694d3adb35b99.webp", + "name_cn": "TREASURER Executive Black 7mg", + "name_jp": "トレジャラー・エグゼクティブ・ブラック", + "description": "", + "price_jpy": 680, + "category": "TREASURER", + "price_cny": 32.64 + }, + { + "external_id": "T930", + "image_url": "https://ribenyan.com/data/images/goods/930_699702c8d0d6b.webp", + "name_cn": "Treasurer Luxury Black 8mg", + "name_jp": "トレジャラー・ラグジュアリー・ブラック", + "description": "每条5包", + "price_jpy": 1500, + "category": "TREASURER", + "price_cny": 72.0 + }, + { + "external_id": "T928", + "image_url": "https://ribenyan.com/data/images/goods/928_699702f1c10bd.webp", + "name_cn": "TREASURER Premium Black 10mg", + "name_jp": "トレジャラー・プレミアム・ブラック", + "description": "每条5包", + "price_jpy": 4000, + "category": "TREASURER", + "price_cny": 192.0 + }, + { + "external_id": "T929", + "image_url": "https://ribenyan.com/data/images/goods/929_699702da8c9f2.webp", + "name_cn": "TREASURER Premier Gold 8mg", + "name_jp": "トレジャラー・プレミアム・ゴールド", + "description": "每条5包", + "price_jpy": 3800, + "category": "TREASURER", + "price_cny": 182.4 + }, + { + "external_id": "T931", + "image_url": "https://ribenyan.com/data/images/goods/931_6997030157fe0.webp", + "name_cn": "Treasurer Premium Silver 6mg", + "name_jp": "トレジャラー・プレミアム・シルバー", + "description": "每条5包", + "price_jpy": 2800, + "category": "TREASURER", + "price_cny": 134.4 + }, + { + "external_id": "3256", + "image_url": "https://ribenyan.com/data/images/goods/246_690c6e7fddea7.webp", + "name_cn": "VIBES", + "name_jp": "バイブス", + "description": "", + "price_jpy": 540, + "category": "VIBES", + "price_cny": 25.92 + }, + { + "external_id": "3141", + "image_url": "https://ribenyan.com/data/images/goods/199_690c629e7312a.webp", + "name_cn": "弗吉尼亚/Virginia S Rose 薄荷 1mg", + "name_jp": "バージニア・エス・ロゼ・メンソール", + "description": "", + "price_jpy": 570, + "category": "Virginia/弗吉尼亚", + "price_cny": 27.36 + }, + { + "external_id": "2299", + "image_url": "https://ribenyan.com/data/images/goods/198_690c627d63183.webp", + "name_cn": "弗吉尼亚/Virginia S Sophia 6mg", + "name_jp": "バージニア・エス・ソフィア6", + "description": "", + "price_jpy": 570, + "category": "Virginia/弗吉尼亚", + "price_cny": 27.36 + }, + { + "external_id": "2774", + "image_url": "https://ribenyan.com/data/images/goods/197_690c5e46ed429.webp", + "name_cn": "弗吉尼亚/Virginia S Sophia 1mg", + "name_jp": "バージニア・エス・ソフィア1", + "description": "", + "price_jpy": 570, + "category": "Virginia/弗吉尼亚", + "price_cny": 27.36 + }, + { + "external_id": "2552", + "image_url": "https://ribenyan.com/data/images/goods/227_690c68287315a.webp", + "name_cn": "WIN 5mg", + "name_jp": "WIN5", + "description": "", + "price_jpy": 430, + "category": "WIN", + "price_cny": 20.64 + }, + { + "external_id": "2550", + "image_url": "https://ribenyan.com/data/images/goods/225_690c67e457139.webp", + "name_cn": "WIN 1mg", + "name_jp": "WIN1", + "description": "", + "price_jpy": 430, + "category": "WIN", + "price_cny": 20.64 + }, + { + "external_id": "2551", + "image_url": "https://ribenyan.com/data/images/goods/226_690c6804cd5b1.webp", + "name_cn": "WIN 3mg", + "name_jp": "WIN3", + "description": "", + "price_jpy": 430, + "category": "WIN", + "price_cny": 20.64 + }, + { + "external_id": "2414", + "image_url": "https://ribenyan.com/data/images/goods/441_68f09a32abf50.webp", + "name_cn": "中华 软盒", + "name_jp": "中華 ソフト", + "description": "", + "price_jpy": 1050, + "category": "中华", + "price_cny": 50.4 + }, + { + "external_id": "2415", + "image_url": "https://ribenyan.com/data/images/goods/241_690c6a6157171.webp", + "name_cn": "中华 硬盒", + "name_jp": "中華ボックス", + "description": "", + "price_jpy": 630, + "category": "中华", + "price_cny": 30.24 + }, + { + "external_id": "3156", + "image_url": "https://ribenyan.com/data/images/goods/245_690c6e667f03a.webp", + "name_cn": "中南海 ONE 薄荷 1mg", + "name_jp": "中南海ワン・メンソール", + "description": "", + "price_jpy": 500, + "category": "中南海", + "price_cny": 24.0 + }, + { + "external_id": "2706", + "image_url": "https://ribenyan.com/data/images/goods/243_690c6a9c51761.webp", + "name_cn": "中南海 淡味 8mg", + "name_jp": "中南海・ライト", + "description": "", + "price_jpy": 520, + "category": "中南海", + "price_cny": 24.96 + }, + { + "external_id": "2065", + "image_url": "https://ribenyan.com/data/images/goods/336_694d3ab88285d.webp", + "name_cn": "中南海 5mg 长支", + "name_jp": "中南海 ファイブ ロング", + "description": "", + "price_jpy": 530, + "category": "中南海", + "price_cny": 25.44 + }, + { + "external_id": "2883", + "image_url": "https://ribenyan.com/data/images/goods/242_690c6a822db22.webp", + "name_cn": "中南海 超淡 3mg", + "name_jp": "中南海・スーパー・ライト", + "description": "", + "price_jpy": 520, + "category": "中南海", + "price_cny": 24.96 + }, + { + "external_id": "3138", + "image_url": "https://ribenyan.com/data/images/goods/244_690c6e3ead318.webp", + "name_cn": "中南海 ONE 1mg", + "name_jp": "中南海ワン", + "description": "", + "price_jpy": 520, + "category": "中南海", + "price_cny": 24.96 + }, + { + "external_id": "2354", + "image_url": "https://ribenyan.com/data/images/goods/287_690d669aee8db.webp", + "name_cn": "双喜/DoubleHappiness 薄荷", + "name_jp": "ダブルハピネス・メンソール", + "description": "", + "price_jpy": 470, + "category": "双喜/DoubleHappiness", + "price_cny": 22.56 + }, + { + "external_id": "2329", + "image_url": "https://ribenyan.com/data/images/goods/284_690d65086a37e.webp", + "name_cn": "双喜/DoubleHappiness 16支 原味", + "name_jp": "ダブルハピネス・16・オリジナル", + "description": "", + "price_jpy": 380, + "category": "双喜/DoubleHappiness", + "price_cny": 18.24 + }, + { + "external_id": "2331", + "image_url": "https://ribenyan.com/data/images/goods/285_690d661aac99f.webp", + "name_cn": "双喜/DoubleHappiness 16支 淡味", + "name_jp": "ダブルハピネス・16・ライト", + "description": "", + "price_jpy": 380, + "category": "双喜/DoubleHappiness", + "price_cny": 18.24 + }, + { + "external_id": "2351", + "image_url": "https://ribenyan.com/data/images/goods/286_690d664ee8b70.webp", + "name_cn": "双喜/DoubleHappiness 原味", + "name_jp": "ダブルハピネス・オリジナル", + "description": "", + "price_jpy": 470, + "category": "双喜/DoubleHappiness", + "price_cny": 22.56 + }, + { + "external_id": "2789", + "image_url": "https://ribenyan.com/data/images/goods/288_690d66ba4d6e8.webp", + "name_cn": "双喜/DoubleHappiness 淡味", + "name_jp": "ダブルハピネス・ライト", + "description": "", + "price_jpy": 470, + "category": "双喜/DoubleHappiness", + "price_cny": 22.56 + }, + { + "external_id": "5003", + "image_url": "https://ribenyan.com/data/images/goods/347_68f0992a9f6c3.webp", + "name_cn": "阿里山 MUSE 5mg 蜜瓜薄荷 硬盒", + "name_jp": "ミューズ5ミリ メロンメンソール ハードボックス", + "description": "", + "price_jpy": 480, + "category": "阿里山", + "price_cny": 23.04 + }, + { + "external_id": "5002", + "image_url": "https://ribenyan.com/data/images/goods/348_68f0fda1bd350.webp", + "name_cn": "阿里山 景泰典蓝 软盒", + "name_jp": "景泰エメラルドブルーソフト", + "description": "", + "price_jpy": 800, + "category": "阿里山", + "price_cny": 38.4 + }, + { + "external_id": "5000", + "image_url": "https://ribenyan.com/data/images/goods/131_68f0fbc091480.webp", + "name_cn": "阿里山 景泰典蓝 中支 硬盒", + "name_jp": "景泰エメラルドブルーミドルタイプ(宽扁)", + "description": "", + "price_jpy": 600, + "category": "阿里山", + "price_cny": 28.8 + }, + { + "external_id": "5001", + "image_url": "https://ribenyan.com/data/images/goods/349_68f0fc6e094a8.webp", + "name_cn": "阿里山 景泰典蓝 硬盒", + "name_jp": "景泰エメラルドブルーボックス", + "description": "", + "price_jpy": 520, + "category": "阿里山", + "price_cny": 24.96 + }, + { + "external_id": "5004", + "image_url": "https://ribenyan.com/data/images/goods/346_68f0992a799b1.webp", + "name_cn": "阿里山 MUSE 8mg 硬盒", + "name_jp": "ミューズ8ミリ ハードボックス", + "description": "", + "price_jpy": 430, + "category": "阿里山", + "price_cny": 20.64 + }, + { + "external_id": "5005", + "image_url": "https://ribenyan.com/data/images/goods/345_68f20d6d61e13.webp", + "name_cn": "阿里山 MUSE 4mg 硬盒", + "name_jp": "ミューズ4ミリ ハードボックス", + "description": "", + "price_jpy": 430, + "category": "阿里山", + "price_cny": 20.64 + }, + { + "external_id": "T948", + "image_url": "https://ribenyan.com/data/images/goods/948_69c12a20cd669.webp", + "name_cn": "黄鹤楼 四季", + "name_jp": "黄鶴楼 こうかくろうFour Seasons", + "description": "", + "price_jpy": 770, + "category": "黄鹤楼", + "price_cny": 36.96 + }, + { + "external_id": "1189", + "image_url": "https://ribenyan.com/data/images/goods/367_68f0992fd2b26.webp", + "name_cn": "骆驼/Camel 麝香葡萄 Menthol Muscat Ploom用", + "name_jp": "キャメル・メンソール・マスカット・プルーム用", + "description": "ploom 专用烟弹", + "price_jpy": 530, + "category": "Camel/骆驼", + "price_cny": 25.44 + }, + { + "external_id": "1434", + "image_url": "https://ribenyan.com/data/images/goods/363_68f0992f82445.webp", + "name_cn": "骆驼/Camel 蓝莓爆珠 Berry Option Ploom用", + "name_jp": "キャメル・ベリー・オプション・プルーム用", + "description": "ploom 专用烟弹,浆果爆珠", + "price_jpy": 530, + "category": "Camel/骆驼", + "price_cny": 25.44 + }, + { + "external_id": "1182", + "image_url": "https://ribenyan.com/data/images/goods/368_68f0992fe4aa7.webp", + "name_cn": "骆驼/Camel 浓原味 Rich Ploom用", + "name_jp": "キャメル・リッチ・プルーム用", + "description": "ploom 专用烟弹", + "price_jpy": 530, + "category": "Camel/骆驼", + "price_cny": 25.44 + }, + { + "external_id": "1183", + "image_url": "https://ribenyan.com/data/images/goods/362_68f0992f6e684.webp", + "name_cn": "骆驼/Camel 淡原味 Smooth Ploom用", + "name_jp": "キャメル・スムース・プルーム用", + "description": "ploom 专用烟弹,清甜雅致,醇和饱满", + "price_jpy": 530, + "category": "Camel/骆驼", + "price_cny": 25.44 + }, + { + "external_id": "1188", + "image_url": "https://ribenyan.com/data/images/goods/366_68f0992fbfba9.webp", + "name_cn": "骆驼/Camel 蓝莓薄荷 Menthol Berry Ploom用", + "name_jp": "キャメル・メンソール・ベリー・プルーム用", + "description": "ploom 专用烟弹", + "price_jpy": 530, + "category": "Camel/骆驼", + "price_cny": 25.44 + }, + { + "external_id": "1184", + "image_url": "https://ribenyan.com/data/images/goods/364_68f0992f95942.webp", + "name_cn": "骆驼/Camel 浓薄荷 Menthol Cold Ploom用", + "name_jp": "キャメル・メンソール・コールド・プルーム用", + "description": "ploom 专用烟弹", + "price_jpy": 530, + "category": "Camel/骆驼", + "price_cny": 25.44 + }, + { + "external_id": "1249", + "image_url": "https://ribenyan.com/data/images/goods/365_68f0992fa8af4.webp", + "name_cn": "骆驼/Camel 淡薄荷 Menthol Fresh Ploom用", + "name_jp": "キャメル・メンソール・フレッシュ・プルーム用", + "description": "ploom 专用烟弹,清凉薄荷", + "price_jpy": 530, + "category": "Camel/骆驼", + "price_cny": 25.44 + }, + { + "external_id": "1995", + "image_url": "https://ribenyan.com/data/images/goods/360_68f09a2e7d323.webp", + "name_cn": "Evo Tropical Berry Crystal Plume", + "name_jp": "エボ・トロピカル・ベリー・クリスタル・プルーム用", + "description": "ploom 专用烟弹", + "price_jpy": 580, + "category": "EVO", + "price_cny": 27.84 + }, + { + "external_id": "1997", + "image_url": "https://ribenyan.com/data/images/goods/765_693abcec25b0c.webp", + "name_cn": "Evo Hachimitsu Lemon Crystal", + "name_jp": "エボ・ハチミツ・レモン・クリスタル・プルーム用", + "description": "ploom 专用烟弹", + "price_jpy": 580, + "category": "EVO", + "price_cny": 27.84 + }, + { + "external_id": "1996", + "image_url": "https://ribenyan.com/data/images/goods/359_68f09a2e6a401.webp", + "name_cn": "Evo Tropical Banana Crystal Plume", + "name_jp": "エボ・トロピカル・バナナ・クリスタル・プルーム用", + "description": "ploom 专用烟弹", + "price_jpy": 580, + "category": "EVO", + "price_cny": 27.84 + }, + { + "external_id": "1992", + "image_url": "https://ribenyan.com/data/images/goods/358_68f09a2e55365.webp", + "name_cn": "Evo Deep Regular", + "name_jp": "エボ・ディープレギュラー", + "description": "ploom 专用烟弹", + "price_jpy": 580, + "category": "EVO", + "price_cny": 27.84 + }, + { + "external_id": "1920", + "image_url": "https://ribenyan.com/data/images/goods/763_693abe3fb9f88.webp", + "name_cn": "Evo Black Menthol", + "name_jp": "エボ・ブラック・メンソール・プルーム用", + "description": "ploom 专用烟弹", + "price_jpy": 580, + "category": "EVO", + "price_cny": 27.84 + }, + { + "external_id": "1994", + "image_url": "https://ribenyan.com/data/images/goods/361_68f09a2e90e63.webp", + "name_cn": "Evo Berry Crystal", + "name_jp": "エボ・ベリー・クリスタル", + "description": "ploom 专用烟弹", + "price_jpy": 580, + "category": "EVO", + "price_cny": 27.84 + }, + { + "external_id": "1921", + "image_url": "https://ribenyan.com/data/images/goods/764_693abe47408d5.webp", + "name_cn": "Evo Fresh Mint", + "name_jp": "エボ・フレッシュ・ミント・プルーム用", + "description": "ploom 专用烟弹", + "price_jpy": 580, + "category": "EVO", + "price_cny": 27.84 + }, + { + "external_id": "1993", + "image_url": "https://ribenyan.com/data/images/goods/357_68f09a2e4074f.webp", + "name_cn": "Evo Cold Menthol", + "name_jp": "エボ・コールド・メンソール", + "description": "ploom 专用烟弹", + "price_jpy": 580, + "category": "EVO", + "price_cny": 27.84 + }, + { + "external_id": "2519", + "image_url": "https://ribenyan.com/data/images/goods/399_690d556c30dad.webp", + "name_cn": "健牌/箭牌/Kent True Berry Boost Hyper专用", + "name_jp": "ケント・トゥルー・ベリー・ブースト・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "2280", + "image_url": "https://ribenyan.com/data/images/goods/398_690d54f885a7a.webp", + "name_cn": "健牌/箭牌/Kent True Tobacco Hyper专用", + "name_jp": "ケント・トゥルー・タバコ・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "2279", + "image_url": "https://ribenyan.com/data/images/goods/401_690d55b8539e3.webp", + "name_cn": "健牌/箭牌/Kent True Rich Tobacco Hyper专用", + "name_jp": "ケント・トゥルー・リッチ・タバコ・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "2004", + "image_url": "https://ribenyan.com/data/images/goods/691_690d5044e979a.webp", + "name_cn": "Kent Neostick Bright Tobacco JW", + "name_jp": "ケント・ネオスティック・ ブライト・タバコ・JW", + "description": "glo pro 专用烟弹", + "price_jpy": 550, + "category": "Kent/健牌/箭牌", + "price_cny": 26.4 + }, + { + "external_id": "2016", + "image_url": "https://ribenyan.com/data/images/goods/695_690d53551b977.webp", + "name_cn": "Kent Neostick Dark Fresh JW", + "name_jp": "ケント・ネオスティック・ダーク・ フレッシュ・JW", + "description": "glo pro 专用烟弹", + "price_jpy": 550, + "category": "Kent/健牌/箭牌", + "price_cny": 26.4 + }, + { + "external_id": "2018", + "image_url": "https://ribenyan.com/data/images/goods/696_690d54430d9f4.webp", + "name_cn": "Kent Neostick Berry Boost JW", + "name_jp": "ケント・ネオスティック・ ベリー・ブースト・JW", + "description": "glo pro 专用烟弹", + "price_jpy": 550, + "category": "Kent/健牌/箭牌", + "price_cny": 26.4 + }, + { + "external_id": "2516", + "image_url": "https://ribenyan.com/data/images/goods/400_690d5591347e5.webp", + "name_cn": "健牌/箭牌/Kent True Menthol Hyper专用", + "name_jp": "ケント・トゥルー・メンソール・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "2487", + "image_url": "https://ribenyan.com/data/images/goods/402_690d55e3b9ea1.webp", + "name_cn": "健牌/箭牌/Kent True Rich Menthol Hyper专用", + "name_jp": "ケント・トゥルー・リッチ・メンソール・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "Kent/健牌/箭牌", + "price_cny": 24.0 + }, + { + "external_id": "2008", + "image_url": "https://ribenyan.com/data/images/goods/693_690d51b370b85.webp", + "name_cn": "Kent Neostick Intensely Fresh JW", + "name_jp": "ケント・ネオスティック・ インテンスリー・フレッシュ・JW", + "description": "glo pro 专用烟弹", + "price_jpy": 550, + "category": "Kent/健牌/箭牌", + "price_cny": 26.4 + }, + { + "external_id": "2006", + "image_url": "https://ribenyan.com/data/images/goods/694_690d524ca87b2.webp", + "name_cn": "Kent Neostick Mint Boost XW", + "name_jp": "ケント・ネオスティック・ミント・ ブースト・エックスW", + "description": "glo pro 专用烟弹", + "price_jpy": 550, + "category": "Kent/健牌/箭牌", + "price_cny": 26.4 + }, + { + "external_id": "2015", + "image_url": "https://ribenyan.com/data/images/goods/692_690d50c192061.webp", + "name_cn": "Kent Neostick Smooth Fresh JW", + "name_jp": "ケント・ネオスティック・スムース・ フレッシュ・JW", + "description": "glo pro 专用烟弹", + "price_jpy": 550, + "category": "Kent/健牌/箭牌", + "price_cny": 26.4 + }, + { + "external_id": "2277", + "image_url": "https://ribenyan.com/data/images/goods/415_690d634d1658e.webp", + "name_cn": "好彩/LuckyStrike Tropical Boost Hyper专用", + "name_jp": "ラッキーストライク・トロピカル・ブースト・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 450, + "category": "LuckyStrike/好彩", + "price_cny": 21.6 + }, + { + "external_id": "2272", + "image_url": "https://ribenyan.com/data/images/goods/417_690d639d96ff6.webp", + "name_cn": "好彩/LuckyStrike Berry Boost Hyper专用", + "name_jp": "ラッキーストライク・ベリー・ブースト・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 450, + "category": "LuckyStrike/好彩", + "price_cny": 21.6 + }, + { + "external_id": "2198", + "image_url": "https://ribenyan.com/data/images/goods/418_690d62e781b50.webp", + "name_cn": "好彩/LuckyStrike Berry Menthol Hyper专用", + "name_jp": "ラッキーストライク・ベリー・メンソール・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 450, + "category": "LuckyStrike/好彩", + "price_cny": 21.6 + }, + { + "external_id": "2611", + "image_url": "https://ribenyan.com/data/images/goods/414_690d631c5f3fa.webp", + "name_cn": "好彩/LuckyStrike Tropical Switch Hyper专用", + "name_jp": "ラッキーストライク・トロピカル・スイッチ・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 450, + "category": "LuckyStrike/好彩", + "price_cny": 21.6 + }, + { + "external_id": "2605", + "image_url": "https://ribenyan.com/data/images/goods/416_690d63784663f.webp", + "name_cn": "好彩/LuckyStrike Berry Switch Hyper专用", + "name_jp": "ラッキーストライク・ベリー・スイッチ・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 450, + "category": "LuckyStrike/好彩", + "price_cny": 21.6 + }, + { + "external_id": "3075", + "image_url": "https://ribenyan.com/data/images/goods/412_690d625b960f4.webp", + "name_cn": "好彩/LuckyStrike Dark Tobacco Hyper专用", + "name_jp": "ラッキーストライク・ダーク・タバコ・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 450, + "category": "LuckyStrike/好彩", + "price_cny": 21.6 + }, + { + "external_id": "2142", + "image_url": "https://ribenyan.com/data/images/goods/419_690d62a62a52f.webp", + "name_cn": "好彩/LuckyStrike Menthol Hyper专用", + "name_jp": "ラッキーストライク・メンソール・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 450, + "category": "LuckyStrike/好彩", + "price_cny": 21.6 + }, + { + "external_id": "3965", + "image_url": "https://ribenyan.com/data/images/goods/420_690d627dcb8e2.webp", + "name_cn": "好彩/LuckyStrike Rich Tobacco Hyper专用", + "name_jp": "ラッキーストライク・リッチ・タバコ・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 450, + "category": "LuckyStrike/好彩", + "price_cny": 21.6 + }, + { + "external_id": "2141", + "image_url": "https://ribenyan.com/data/images/goods/413_690d62c654a84.webp", + "name_cn": "好彩/LuckyStrike Dark Menthol Hyper专用", + "name_jp": "ラッキーストライク・ダーク・メンソール・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 450, + "category": "LuckyStrike/好彩", + "price_cny": 21.6 + }, + { + "external_id": "1878", + "image_url": "https://ribenyan.com/data/images/goods/378_68f099d331d63.webp", + "name_cn": "梅比乌斯/MEVIUS 蓝莓爆珠 Berry Option Ploom用", + "name_jp": "メビウス・ベリー・オプション・プルーム用", + "description": "ploom 专用烟弹,浆果爆珠", + "price_jpy": 550, + "category": "MEVIUS/梅比乌斯", + "price_cny": 26.4 + }, + { + "external_id": "1880", + "image_url": "https://ribenyan.com/data/images/goods/379_68f099d34565c.webp", + "name_cn": "梅比乌斯/MEVIUS 麝香葡萄爆珠 Muscat Option Ploom用", + "name_jp": "メビウス・マスカット・オプション・プルーム用", + "description": "ploom 专用烟弹,麝香葡萄爆珠", + "price_jpy": 550, + "category": "MEVIUS/梅比乌斯", + "price_cny": 26.4 + }, + { + "external_id": "1881", + "image_url": "https://ribenyan.com/data/images/goods/369_68f0993004871.webp", + "name_cn": "梅比乌斯/MEVIUS 苹果爆珠 Apple Option Ploom用", + "name_jp": "メビウス・アップル・オプション・プルーム用", + "description": "ploom 专用烟弹,苹果爆珠", + "price_jpy": 550, + "category": "MEVIUS/梅比乌斯", + "price_cny": 26.4 + }, + { + "external_id": "1621", + "image_url": "https://ribenyan.com/data/images/goods/370_68f0993017ae1.webp", + "name_cn": "梅比乌斯/MEVIUS 浓香原味 Aroma Rich Regular Ploom用", + "name_jp": "メビウス・アロマリッチ・レギュラー・プルーム用", + "description": "ploom 专用烟弹,香气浓郁", + "price_jpy": 550, + "category": "MEVIUS/梅比乌斯", + "price_cny": 26.4 + }, + { + "external_id": "1879", + "image_url": "https://ribenyan.com/data/images/goods/372_68f099d2ade33.webp", + "name_cn": "梅比乌斯/MEVIUS 柠檬爆珠 Citrus Option Ploom用", + "name_jp": "メビウス・シトラス・オプション・プルーム用", + "description": "ploom 专用烟弹,柑橘爆珠", + "price_jpy": 550, + "category": "MEVIUS/梅比乌斯", + "price_cny": 26.4 + }, + { + "external_id": "1247", + "image_url": "https://ribenyan.com/data/images/goods/377_68f099d31f935.webp", + "name_cn": "梅比乌斯/MEVIUS 梨爆珠 Pear Option Ploom用", + "name_jp": "メビウス・ペア・オプション・プルーム用", + "description": "ploom 专用烟弹,梨爆珠", + "price_jpy": 550, + "category": "MEVIUS/梅比乌斯", + "price_cny": 26.4 + }, + { + "external_id": "1877", + "image_url": "https://ribenyan.com/data/images/goods/373_68f099d2c6c1a.webp", + "name_cn": "梅比乌斯/MEVIUS 浓薄荷 Sharp Cold Menthol Ploom用", + "name_jp": "メビウス・シャープ・コールド・メンソール・プルーム用", + "description": "ploom 专用烟弹,强劲冰爽薄荷", + "price_jpy": 550, + "category": "MEVIUS/梅比乌斯", + "price_cny": 26.4 + }, + { + "external_id": "1874", + "image_url": "https://ribenyan.com/data/images/goods/375_68f099d2ed2da.webp", + "name_cn": "梅比乌斯/MEVIUS 浓原味 Deep Regular Ploom用", + "name_jp": "メビウス・ディープ・レギュラー・プルーム用", + "description": "ploom 专用烟弹,醇香浓厚,层次深邃", + "price_jpy": 550, + "category": "MEVIUS/梅比乌斯", + "price_cny": 26.4 + }, + { + "external_id": "1876", + "image_url": "https://ribenyan.com/data/images/goods/371_68f099302bb06.webp", + "name_cn": "梅比乌斯/MEVIUS 淡薄荷 Cold Menthol Ploom用", + "name_jp": "メビウス・コールド・メンソール・プルーム用", + "description": "ploom 专用烟弹,冰爽薄荷", + "price_jpy": 550, + "category": "MEVIUS/梅比乌斯", + "price_cny": 26.4 + }, + { + "external_id": "1875", + "image_url": "https://ribenyan.com/data/images/goods/374_68f099d2d9bb3.webp", + "name_cn": "梅比乌斯/MEVIUS 淡原味 Smooth Regular Ploom用", + "name_jp": "メビウス・スムース・レギュラー・プルーム用", + "description": "ploom 专用烟弹,绝妙平衡,醇和风味", + "price_jpy": 550, + "category": "MEVIUS/梅比乌斯", + "price_cny": 26.4 + }, + { + "external_id": "1615", + "image_url": "https://ribenyan.com/data/images/goods/376_68f099d30c94c.webp", + "name_cn": "梅比乌斯/MEVIUS 黑薄荷 Black Cold Menthol Ploom用", + "name_jp": "メビウス・ブラック・コールド・メンソール・プルーム用", + "description": "ploom 专用烟弹,持久留存的极冷薄荷", + "price_jpy": 550, + "category": "MEVIUS/梅比乌斯", + "price_cny": 26.4 + }, + { + "external_id": "1620", + "image_url": "https://ribenyan.com/data/images/goods/380_68f099d35989c.webp", + "name_cn": "梅比乌斯/MEVIUS 混合薄荷 Mix Mint Menthol Ploom用", + "name_jp": "メビウス・ミックス・ミント・メンソール・プルーム用", + "description": "ploom 专用烟弹,混合薄荷薄荷醇", + "price_jpy": 550, + "category": "MEVIUS/梅比乌斯", + "price_cny": 26.4 + }, + { + "external_id": "2703", + "image_url": "https://ribenyan.com/data/images/goods/409_690d5ec789cff.webp", + "name_cn": "NEO Brilliant Muscat", + "name_jp": "ネオ・ブリリアント・マスカット", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "NEO", + "price_cny": 24.0 + }, + { + "external_id": "2687", + "image_url": "https://ribenyan.com/data/images/goods/129_690d589e11e80.webp", + "name_cn": "NEO Brilliant Berry (glo hyper)", + "name_jp": "ネオ・ブリリアント・ベリー・hyper用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "NEO", + "price_cny": 24.0 + }, + { + "external_id": "2666", + "image_url": "https://ribenyan.com/data/images/goods/407_690d586d71225.webp", + "name_cn": "NEO Brilliant Tropical Hyper专用", + "name_jp": "ネオ・ブリリアント・トロピカル・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "NEO", + "price_cny": 24.0 + }, + { + "external_id": "2664", + "image_url": "https://ribenyan.com/data/images/goods/410_690d5fb58ddac.webp", + "name_cn": "NEO Brilliant Red Fruits Hyper专用", + "name_jp": "ネオ・ブリリアント・レッドフルーツ・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "NEO", + "price_cny": 24.0 + }, + { + "external_id": "2709", + "image_url": "https://ribenyan.com/data/images/goods/411_690d6035699d7.webp", + "name_cn": "NEO Brilliant Wildberry", + "name_jp": "ネオ・ブリリアント・ワイルドベリー", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "NEO", + "price_cny": 24.0 + }, + { + "external_id": "2683", + "image_url": "https://ribenyan.com/data/images/goods/403_690d56434736b.webp", + "name_cn": "NEO Arctic Menthol Hyper专用", + "name_jp": "ネオ・アークティック・メンソール・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "NEO", + "price_cny": 24.0 + }, + { + "external_id": "2669", + "image_url": "https://ribenyan.com/data/images/goods/404_690d567a6fb76.webp", + "name_cn": "NEO Iced Menthol Hyper专用", + "name_jp": "ネオ・アイスド・メンソール・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "NEO", + "price_cny": 24.0 + }, + { + "external_id": "2668", + "image_url": "https://ribenyan.com/data/images/goods/405_690d56fdd15e7.webp", + "name_cn": "NEO Classic Tobacco Hyper专用", + "name_jp": "ネオ・クラシック・タバコ・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "NEO", + "price_cny": 24.0 + }, + { + "external_id": "2834", + "image_url": "https://ribenyan.com/data/images/goods/701_690d6121b31ed.webp", + "name_cn": "Neo Chilled Menthol for hyper F", + "name_jp": "ネオ・チルド・メンソール・ hyper用F", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "NEO", + "price_cny": 24.0 + }, + { + "external_id": "2686", + "image_url": "https://ribenyan.com/data/images/goods/406_690d60993f0ac.webp", + "name_cn": "NEO Chilled Menthol Hyper专用", + "name_jp": "ネオ・チルド・メンソール・ハイパー用", + "description": "glo Hyper 专用烟弹", + "price_jpy": 500, + "category": "NEO", + "price_cny": 24.0 + }, + { + "external_id": "2486", + "image_url": "https://ribenyan.com/data/images/goods/386_690c75d6bcd1f.webp", + "name_cn": "SENTIA 西瓜冰 Juicy Red", + "name_jp": "センティア・ジューシー・レッド", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2286", + "image_url": "https://ribenyan.com/data/images/goods/390_690c76c9e2492.webp", + "name_cn": "SENTIA 热带水果 Tropical Yellow", + "name_jp": "センティア・トロピカル・イエロー", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2287", + "image_url": "https://ribenyan.com/data/images/goods/391_690c792e0a96f.webp", + "name_cn": "SENTIA 淡香草原味 Balanced Yellow", + "name_jp": "センティア・バランスド・イエロー", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2284", + "image_url": "https://ribenyan.com/data/images/goods/387_690c760e9e995.webp", + "name_cn": "SENTIA 柔和原味 Smooth Gold E", + "name_jp": "センティア・スムース・ゴールド E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2289", + "image_url": "https://ribenyan.com/data/images/goods/393_690c7960500b2.webp", + "name_cn": "SENTIA 浓原味 Pure Teak", + "name_jp": "センティア・ピュア・ティーク", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2292", + "image_url": "https://ribenyan.com/data/images/goods/396_690c7a4f505aa.webp", + "name_cn": "SENTIA 蓝莓 Fresh Purple", + "name_jp": "センティア・フレッシュ・パープル", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2285", + "image_url": "https://ribenyan.com/data/images/goods/389_690c770a28745.webp", + "name_cn": "SENTIA 超浓原味 Deep Bronze", + "name_jp": "センティア・ディープ・ブロンズ", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2809", + "image_url": "https://ribenyan.com/data/images/goods/766_693ac0d5a3f77.webp", + "name_cn": "SENTIA Icy Red", + "name_jp": "センティア・アイシー・レッド", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2283", + "image_url": "https://ribenyan.com/data/images/goods/385_690c756823e23.webp", + "name_cn": "SENTIA 青柠 Citrus Green", + "name_jp": "センティア・シトラス・グリーン", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2282", + "image_url": "https://ribenyan.com/data/images/goods/384_690c74a5330dc.webp", + "name_cn": "SENTIA 坚果 Clear Silver", + "name_jp": "センティア・クリア・シルバー", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2294", + "image_url": "https://ribenyan.com/data/images/goods/397_690c7a7c3d54f.webp", + "name_cn": "SENTIA 浓薄荷 Frost Green", + "name_jp": "センティア・フロスト・グリーン", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2561", + "image_url": "https://ribenyan.com/data/images/goods/395_690c79d89f07a.webp", + "name_cn": "SENTIA 珊瑚橙 Fresh Coral", + "name_jp": "センティア・フレッシュ・コーラル", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2205", + "image_url": "https://ribenyan.com/data/images/goods/381_690c73123ec78.webp", + "name_cn": "SENTIA 黑蓝莓 Icy Purple", + "name_jp": "センティア・アイシー・パープル", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2601", + "image_url": "https://ribenyan.com/data/images/goods/388_690c765ae6790.webp", + "name_cn": "SENTIA 紫罗兰 Smooth Violet", + "name_jp": "センティア・スムース・バイオレット", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2273", + "image_url": "https://ribenyan.com/data/images/goods/277_690c732eb9a71.webp", + "name_cn": "SENTIA 黑薄荷 Ice Black E", + "name_jp": "センティア・アイシー・ブラック E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2274", + "image_url": "https://ribenyan.com/data/images/goods/383_690c74537bac7.webp", + "name_cn": "SENTIA 薄荷 Cool Jade", + "name_jp": "センティア・クール・ジェイド", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2291", + "image_url": "https://ribenyan.com/data/images/goods/394_690c799cd80c5.webp", + "name_cn": "SENTIA 温和薄荷 Fresh Emerald", + "name_jp": "センティア・フレッシュ・エメラルド", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 570, + "category": "SENTIA", + "price_cny": 27.36 + }, + { + "external_id": "2392", + "image_url": "https://ribenyan.com/data/images/goods/113_690c972b3e93c.webp", + "name_cn": "TEREA 百香果爆珠/绿洲爆珠 Oasis Pearl E", + "name_jp": "テリア・オアシス・パール E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2604", + "image_url": "https://ribenyan.com/data/images/goods/429_690c9a1a9d48e.webp", + "name_cn": "TEREA 青提爆珠 Shine Pearl", + "name_jp": "テリア・シャイン・パール", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2393", + "image_url": "https://ribenyan.com/data/images/goods/114_690c9788a767c.webp", + "name_cn": "TEREA 西瓜爆珠 Sun Pearl E", + "name_jp": "テリア・サン・パール E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2327", + "image_url": "https://ribenyan.com/data/images/goods/279_690ca469abc4a.webp", + "name_cn": "TEREA 浓原味 Regular E", + "name_jp": "テリア・レギュラー E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2613", + "image_url": "https://ribenyan.com/data/images/goods/430_690ca28d4c8ff.webp", + "name_cn": "TEREA 黑冰水蜜桃/黑蜜桃薄荷 Black Fusia Menthol", + "name_jp": "テリア・ブラック・フューシャ・メンソール", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2318", + "image_url": "https://ribenyan.com/data/images/goods/127_690ca24aa9fe4.webp", + "name_cn": "TEREA 黑冰蓝莓 Black Purple Menthol E", + "name_jp": "テリア・ブラック・パープル・メンソール E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2325", + "image_url": "https://ribenyan.com/data/images/goods/275_690ca3fe526c3.webp", + "name_cn": "TEREA 超浓原味 Rich Regular E", + "name_jp": "テリア・リッチ・レギュラー E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2562", + "image_url": "https://ribenyan.com/data/images/goods/276_690ca4266988c.webp", + "name_cn": "TEREA 花香爆珠/樱花爆珠 Riviera Pearl", + "name_jp": "テリア・リビエラ・パール", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2707", + "image_url": "https://ribenyan.com/data/images/goods/579_690ca34239686.webp", + "name_cn": "TEREA 蓝莓爆珠 Velvet Pearl", + "name_jp": "テリア・ベルベット・パール", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2823", + "image_url": "https://ribenyan.com/data/images/goods/812_6964fbb321dff.webp", + "name_cn": "TEREA 星光爆珠 Stellar Pearl", + "name_jp": "テリア・ステラー・パール", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2315", + "image_url": "https://ribenyan.com/data/images/goods/126_690ca09fd18a9.webp", + "name_cn": "TEREA 黑冰薄荷 Black Menthol E", + "name_jp": "テリア・ブラック・メンソール E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2303", + "image_url": "https://ribenyan.com/data/images/goods/116_690c9b1f29971.webp", + "name_cn": "TEREA 坚果 Smooth Regular E", + "name_jp": "テリア・スムース・レギュラー E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2386", + "image_url": "https://ribenyan.com/data/images/goods/128_690ca30b50f82.webp", + "name_cn": "TEREA 黑冰野莓/黑莓果 Black Ruby Menthol", + "name_jp": "テリア・ブラック・ルビー・メンソール", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2306", + "image_url": "https://ribenyan.com/data/images/goods/120_690c9e9483164.webp", + "name_cn": "TEREA 淡原味 Balanced Regular E", + "name_jp": "テリア・バランスド・レギュラー E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2535", + "image_url": "https://ribenyan.com/data/images/goods/122_690c9ff9e70c9.webp", + "name_cn": "TEREA 黑冰阳光/柑橘 Black Sunshine Menthol", + "name_jp": "テリア・ブラック・サンシャイン・メンソール", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2310", + "image_url": "https://ribenyan.com/data/images/goods/121_690c9f99044b8.webp", + "name_cn": "TEREA 水蜜桃/花香 Fusion Menthol E", + "name_jp": "テリア・フュージョン・メンソール E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2296", + "image_url": "https://ribenyan.com/data/images/goods/422_690c7ae3997ac.webp", + "name_cn": "TEREA 檀木/雪松 Warm Regular E", + "name_jp": "テリア・ウォーム・レギュラー E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2322", + "image_url": "https://ribenyan.com/data/images/goods/274_690ca3d5057c4.webp", + "name_cn": "TEREA 浓薄荷 Menthol E", + "name_jp": "テリア・メンソール E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2321", + "image_url": "https://ribenyan.com/data/images/goods/273_690ca3a44ca8a.webp", + "name_cn": "TEREA 淡薄荷 Mint E", + "name_jp": "テリア・ミント E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2316", + "image_url": "https://ribenyan.com/data/images/goods/124_690ca070371c0.webp", + "name_cn": "TEREA 黑冰青柠/黑柠檬 Black Yellow Menthol E", + "name_jp": "テリア・ブラック・イエロー・メンソール E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2326", + "image_url": "https://ribenyan.com/data/images/goods/278_690ca448c64d5.webp", + "name_cn": "TEREA 红宝石/梅子原味 Ruby Regular E", + "name_jp": "テリア・ルビー・レギュラー E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2295", + "image_url": "https://ribenyan.com/data/images/goods/111_690c7aa569a49.webp", + "name_cn": "TEREA 青柠 Yellow Menthol E", + "name_jp": "テリア・イエロー・メンソール E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2305", + "image_url": "https://ribenyan.com/data/images/goods/112_690c9e6d9fa16.webp", + "name_cn": "TEREA 蓝莓 Purple Menthol E", + "name_jp": "テリア・パープル・メンソール E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2304", + "image_url": "https://ribenyan.com/data/images/goods/117_690c9b7c66339.webp", + "name_cn": "TEREA 热带水果 Tropical Menthol E", + "name_jp": "テリア・トロピカル・メンソール E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2775", + "image_url": "https://ribenyan.com/data/images/goods/689_690c99c0e1acd.webp", + "name_cn": "TEREA 柠檬 Clear Regular", + "name_jp": "テリア・クリア・レギュラー", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2314", + "image_url": "https://ribenyan.com/data/images/goods/435_690c9fd34fbba.webp", + "name_cn": "TEREA 洋梨 Bright Menthol E", + "name_jp": "テリア・ブライト・メンソール E", + "description": "IQOS ILUMA 专用烟弹", + "price_jpy": 620, + "category": "TEREA", + "price_cny": 29.76 + }, + { + "external_id": "2717", + "image_url": "https://ribenyan.com/data/images/goods/572_690d4de1089d5.webp", + "name_cn": "Virto Dark Tobacco", + "name_jp": "ヴァルト・ダーク・タバコ", + "description": "GLO HILO 专用烟弹", + "price_jpy": 580, + "category": "VIRTO", + "price_cny": 27.84 + }, + { + "external_id": "2819", + "image_url": "https://ribenyan.com/data/images/goods/576_690d4edfd9152.webp", + "name_cn": "Virto Melon Click", + "name_jp": "ヴァルト・メロン・クリック", + "description": "GLO HILO 专用烟弹", + "price_jpy": 580, + "category": "VIRTO", + "price_cny": 27.84 + }, + { + "external_id": "2743", + "image_url": "https://ribenyan.com/data/images/goods/932_69a58ded39b8f.webp", + "name_cn": "Virto Bright Cherry Click", + "name_jp": "ヴァルト・ブライト・チェリー・クリック", + "description": "GLO HILO 专用烟弹", + "price_jpy": 580, + "category": "VIRTO", + "price_cny": 27.84 + }, + { + "external_id": "2798", + "image_url": "https://ribenyan.com/data/images/goods/570_690ca55d5dd6d.webp", + "name_cn": "Virto Iced Berry Click", + "name_jp": "ヴァルト・アイスド・ベリー・クリック", + "description": "GLO HILO 专用烟弹", + "price_jpy": 580, + "category": "VIRTO", + "price_cny": 27.84 + }, + { + "external_id": "2794", + "image_url": "https://ribenyan.com/data/images/goods/574_690d4e649d15a.webp", + "name_cn": "Virto Berry Click", + "name_jp": "ヴァルト・ベリー・クリック", + "description": "GLO HILO 专用烟弹", + "price_jpy": 580, + "category": "VIRTO", + "price_cny": 27.84 + }, + { + "external_id": "2785", + "image_url": "https://ribenyan.com/data/images/goods/575_690d4e89cf4d3.webp", + "name_cn": "Virto Mint Click", + "name_jp": "ヴァルト・ミント・クリック", + "description": "GLO HILO 专用烟弹", + "price_jpy": 580, + "category": "VIRTO", + "price_cny": 27.84 + }, + { + "external_id": "2762", + "image_url": "https://ribenyan.com/data/images/goods/571_690ca584c3e0e.webp", + "name_cn": "Virto Iced Menthol Click", + "name_jp": "ヴァルト・アイスド・メンソール・クリック", + "description": "GLO HILO 专用烟弹", + "price_jpy": 580, + "category": "VIRTO", + "price_cny": 27.84 + }, + { + "external_id": "2766", + "image_url": "https://ribenyan.com/data/images/goods/577_690d4f12ddc6e.webp", + "name_cn": "Virto Menthol Click", + "name_jp": "ヴァルト・メンソール・クリック", + "description": "GLO HILO 专用烟弹", + "price_jpy": 580, + "category": "VIRTO", + "price_cny": 27.84 + }, + { + "external_id": "2755", + "image_url": "https://ribenyan.com/data/images/goods/573_690d4e36e6e28.webp", + "name_cn": "Virto Balanced Tobacco", + "name_jp": "ヴァルト・バランスド・タバコ", + "description": "GLO HILO 专用烟弹", + "price_jpy": 580, + "category": "VIRTO", + "price_cny": 27.84 + }, + { + "external_id": "T923", + "image_url": "https://ribenyan.com/data/images/goods/923_6996bd426ca20.webp", + "name_cn": "glo Hyper Pro 曜石黑", + "name_jp": "glo グロー ハイパー プロ オブシディアン ブラック", + "description": "", + "price_jpy": 4270, + "category": "glo", + "price_cny": 204.96 + }, + { + "external_id": "T922", + "image_url": "https://ribenyan.com/data/images/goods/922_6996bd5b77f26.webp", + "name_cn": "glo Hyper Pro 天青蓝", + "name_jp": "glo グロー ハイパー プロ ラピス ブルー", + "description": "", + "price_jpy": 4270, + "category": "glo", + "price_cny": 204.96 + }, + { + "external_id": "T759", + "image_url": "https://ribenyan.com/data/images/goods/759_6937ceb95239c.webp", + "name_cn": "IQOS ILUMA i ONE 一体机 午夜黑 新款", + "name_jp": "IQOS イルマ i ワン ミッドナイトブラック", + "description": "", + "price_jpy": 6980, + "category": "IQOS", + "price_cny": 335.04 + }, + { + "external_id": "T751", + "image_url": "https://ribenyan.com/data/images/goods/751_6937b3c0707d6.webp", + "name_cn": "IQOS ILUMA i Prime 翻盖 午夜黑", + "name_jp": "IQOS イルマ i プライム ミッドナイトブラック", + "description": "", + "price_jpy": 13980, + "category": "IQOS", + "price_cny": 671.04 + }, + { + "external_id": "T745", + "image_url": "https://ribenyan.com/data/images/goods/745_6937b04fa8fad.webp", + "name_cn": "IQOS ILUMA i 侧开 午夜黑", + "name_jp": "IQOS イルマ i ミッドナイトブラック", + "description": "", + "price_jpy": 10980, + "category": "IQOS", + "price_cny": 527.04 + }, + { + "external_id": "T757", + "image_url": "https://ribenyan.com/data/images/goods/757_6937ceea07307.webp", + "name_cn": "IQOS ILUMA i ONE 一体机 微风蓝 新款", + "name_jp": "IQOS イルマ i ワン ブリーズブルー", + "description": "", + "price_jpy": 6780, + "category": "IQOS", + "price_cny": 325.44 + }, + { + "external_id": "T750", + "image_url": "https://ribenyan.com/data/images/goods/750_6937b3dd5c41c.webp", + "name_cn": "IQOS ILUMA i Prime 翻盖 微风蓝", + "name_jp": "IQOS イルマ i プライム ブリーズブルー", + "description": "", + "price_jpy": 13780, + "category": "IQOS", + "price_cny": 661.44 + }, + { + "external_id": "T752", + "image_url": "https://ribenyan.com/data/images/goods/752_6937b404abc8e.webp", + "name_cn": "IQOS ILUMA i Prime 翻盖 山杨青", + "name_jp": "IQOS イルマ i プライム アスペングリーン", + "description": "", + "price_jpy": 13980, + "category": "IQOS", + "price_cny": 671.04 + }, + { + "external_id": "T738", + "image_url": "https://ribenyan.com/data/images/goods/738_69366e3ac723d.webp", + "name_cn": "IQOS ILUMA i ONE 一体机 午夜黑", + "name_jp": "IQOS イルマ i ワン ミッドナイトブラック", + "description": "", + "price_jpy": 6480, + "category": "IQOS", + "price_cny": 311.04 + }, + { + "external_id": "T744", + "image_url": "https://ribenyan.com/data/images/goods/744_6937b054403ea.webp", + "name_cn": "IQOS ILUMA i 侧开 微风蓝", + "name_jp": "IQOS イルマ i ブリーズブルー", + "description": "", + "price_jpy": 10780, + "category": "IQOS", + "price_cny": 517.44 + }, + { + "external_id": "T740", + "image_url": "https://ribenyan.com/data/images/goods/740_6936d89ee707f.webp", + "name_cn": "IQOS ILUMA i ONE 一体机 微风蓝", + "name_jp": "IQOS イルマ i ワン ブリーズブルー", + "description": "", + "price_jpy": 6280, + "category": "IQOS", + "price_cny": 301.44 + }, + { + "external_id": "T761", + "image_url": "https://ribenyan.com/data/images/goods/761_6937e7d07f45f.webp", + "name_cn": "IQOS ILUMA i 侧开 银河蓝限量版", + "name_jp": "IQOS ILUMA i ギャラクシーブルー", + "description": "", + "price_jpy": 12980, + "category": "IQOS", + "price_cny": 623.04 + }, + { + "external_id": "T760", + "image_url": "https://ribenyan.com/data/images/goods/760_69491e71550ed.webp", + "name_cn": "IQOS ILUMA i ONE 一体机 数电紫 新款", + "name_jp": "IQOS イルマ i ワン デジタルバイオレット", + "description": "", + "price_jpy": 7480, + "category": "IQOS", + "price_cny": 359.04 + }, + { + "external_id": "T943", + "image_url": "https://ribenyan.com/data/images/goods/943_69b93f63e01ed.webp", + "name_cn": "IQOS ILUMA i PRIME 翻盖 电光紫", + "name_jp": "IQOS イルマ i プライム エレクトリックパープル", + "description": "", + "price_jpy": 14980, + "category": "IQOS", + "price_cny": 719.04 + }, + { + "external_id": "T813", + "image_url": "https://ribenyan.com/data/images/goods/813_69829aed4043f.webp", + "name_cn": "IQOS ILUMA i ONE 一体机 电光紫", + "name_jp": "IQOS イルマ i ワンエレクトリックパープル", + "description": "", + "price_jpy": 7980, + "category": "IQOS", + "price_cny": 383.04 + }, + { + "external_id": "T758", + "image_url": "https://ribenyan.com/data/images/goods/758_69492951c23a4.webp", + "name_cn": "IQOS ILUMA i ONE 一体机 叶片绿 新款", + "name_jp": "IQOS イルマ i ワン リーフグリーン", + "description": "", + "price_jpy": 7480, + "category": "IQOS", + "price_cny": 359.04 + }, + { + "external_id": "T814", + "image_url": "https://ribenyan.com/data/images/goods/814_69829ea0ccf38.webp", + "name_cn": "IQOS ILUMA i 侧开 电光紫", + "name_jp": "IQOS イルマ i エレクトリックパープル", + "description": "", + "price_jpy": 11980, + "category": "IQOS", + "price_cny": 575.04 + }, + { + "external_id": "T743", + "image_url": "https://ribenyan.com/data/images/goods/743_6937a29d8ce45.webp", + "name_cn": "IQOS ILUMA i 侧开 数电紫", + "name_jp": "IQOS イルマ i デジタルバイオレット", + "description": "", + "price_jpy": 10980, + "category": "IQOS", + "price_cny": 527.04 + }, + { + "external_id": "T741", + "image_url": "https://ribenyan.com/data/images/goods/741_6936d8e075918.webp", + "name_cn": "IQOS ILUMA i ONE 一体机 叶片绿", + "name_jp": "IQOS イルマ i ワン リーフグリーン", + "description": "", + "price_jpy": 6980, + "category": "IQOS", + "price_cny": 335.04 + }, + { + "external_id": "T749", + "image_url": "https://ribenyan.com/data/images/goods/749_6937b3d77cae7.webp", + "name_cn": "IQOS ILUMA i Prime 翻盖 石榴红", + "name_jp": "IQOS イルマ i プライム ガーネットレッド", + "description": "", + "price_jpy": 14980, + "category": "IQOS", + "price_cny": 719.04 + }, + { + "external_id": "T747", + "image_url": "https://ribenyan.com/data/images/goods/747_6937b05b6fb9a.webp", + "name_cn": "IQOS ILUMA i 侧开 叶片绿", + "name_jp": "IQOS イルマ i リーフグリーン", + "description": "", + "price_jpy": 10980, + "category": "IQOS", + "price_cny": 527.04 + }, + { + "external_id": "T746", + "image_url": "https://ribenyan.com/data/images/goods/746_6937b049585ff.webp", + "name_cn": "IQOS ILUMA i 侧开 陶土红", + "name_jp": "IQOS イルマ i ビビッドテラコッタ", + "description": "", + "price_jpy": 11980, + "category": "IQOS", + "price_cny": 575.04 + }, + { + "external_id": "T804", + "image_url": "https://ribenyan.com/data/images/goods/804_695e8c0d08e21.webp", + "name_cn": "ploom aura 亮玄黑", + "name_jp": "プルーム・オーラ ジェットブラック", + "description": "", + "price_jpy": 4280, + "category": "ploom", + "price_cny": 205.44 + }, + { + "external_id": "T805", + "image_url": "https://ribenyan.com/data/images/goods/805_695e8c7db15e8.webp", + "name_cn": "ploom aura 深海蓝", + "name_jp": "プルーム・オーラ ネイビーブルー", + "description": "", + "price_jpy": 4280, + "category": "ploom", + "price_cny": 205.44 + }, + { + "external_id": "T806", + "image_url": "https://ribenyan.com/data/images/goods/806_695e8c925223c.webp", + "name_cn": "ploom aura 玫瑰金", + "name_jp": "プルーム・オーラ ローズゴールド", + "description": "", + "price_jpy": 4280, + "category": "ploom", + "price_cny": 205.44 + }, + { + "external_id": "2821", + "image_url": "https://ribenyan.com/data/images/goods/786_694d2a6abe9d5.webp", + "name_cn": "Acrema Blend 20g装", + "name_jp": "アクレマ・ブレンド", + "description": "带纸", + "price_jpy": 700, + "category": "Acrema Blend", + "price_cny": 33.6 + }, + { + "external_id": "2265", + "image_url": "https://ribenyan.com/data/images/goods/551_694c087f53292.webp", + "name_cn": "Amber Leaf Organic Blend 25g装", + "name_jp": "アンバーリーフ・オーガニックブレンド(25g)", + "description": "带纸", + "price_jpy": 730, + "category": "Amber Leaf/琥珀", + "price_cny": 35.04 + }, + { + "external_id": "4568", + "image_url": "https://ribenyan.com/data/images/goods/511_694bc888f415d.webp", + "name_cn": "Amber Leaf 25g装", + "name_jp": "アンバーリーフ(25g)", + "description": "带纸", + "price_jpy": 700, + "category": "Amber Leaf/琥珀", + "price_cny": 33.6 + }, + { + "external_id": "4569", + "image_url": "https://ribenyan.com/data/images/goods/543_690388f53cc6b.webp", + "name_cn": "Amber Leaf Blonde 25g装", + "name_jp": "アンバーリーフ・ブロンド(25g)", + "description": "带纸", + "price_jpy": 700, + "category": "Amber Leaf/琥珀", + "price_cny": 33.6 + }, + { + "external_id": "3884", + "image_url": "https://ribenyan.com/data/images/goods/532_690626003686f.webp", + "name_cn": "Amsterdamer Grape Ice 25g装", + "name_jp": "アムステルダマー・グレープアイス(25g)", + "description": "带纸", + "price_jpy": 700, + "category": "Amsterdamer/阿姆斯特丹", + "price_cny": 33.6 + }, + { + "external_id": "3729", + "image_url": "https://ribenyan.com/data/images/goods/531_6906262edab37.webp", + "name_cn": "Amsterdamer Coffee Ice 25g装", + "name_jp": "アムステルダマー・コーヒーアイス(25g)", + "description": "带纸", + "price_jpy": 700, + "category": "Amsterdamer/阿姆斯特丹", + "price_cny": 33.6 + }, + { + "external_id": "3730", + "image_url": "https://ribenyan.com/data/images/goods/542_6906260ecd5ce.webp", + "name_cn": "Amsterdamer Vanilla Ice 25g装", + "name_jp": "アムステルダマー・バニラアイス(25g)", + "description": "带纸", + "price_jpy": 700, + "category": "Amsterdamer/阿姆斯特丹", + "price_cny": 33.6 + }, + { + "external_id": "2170", + "image_url": "https://ribenyan.com/data/images/goods/529_690626385f11d.webp", + "name_cn": "Amsterdamer 100% Tobacco 25g装", + "name_jp": "アムステルダマー・100%タバック(25g)", + "description": "带纸", + "price_jpy": 700, + "category": "Amsterdamer/阿姆斯特丹", + "price_cny": 33.6 + }, + { + "external_id": "2171", + "image_url": "https://ribenyan.com/data/images/goods/533_690625f9b46b8.webp", + "name_cn": "Amsterdamer Original 25g装", + "name_jp": "アムステルダマー・オリジナル(25g)", + "description": "带纸", + "price_jpy": 700, + "category": "Amsterdamer/阿姆斯特丹", + "price_cny": 33.6 + }, + { + "external_id": "3886", + "image_url": "https://ribenyan.com/data/images/goods/530_69062613b81c2.webp", + "name_cn": "Amsterdamer Menthol Ice 25g装", + "name_jp": "アムステルダマー・メンソールアイス(25g)", + "description": "带纸", + "price_jpy": 700, + "category": "Amsterdamer/阿姆斯特丹", + "price_cny": 33.6 + }, + { + "external_id": "3728", + "image_url": "https://ribenyan.com/data/images/goods/534_69062689670cc.webp", + "name_cn": "Amsterdamer Apple Ice 25g装", + "name_jp": "アムステルダマー・アップルアイス(25g)", + "description": "带纸", + "price_jpy": 700, + "category": "Amsterdamer/阿姆斯特丹", + "price_cny": 33.6 + }, + { + "external_id": "2470", + "image_url": "https://ribenyan.com/data/images/goods/536_69062608cec30.webp", + "name_cn": "Amsterdamer XXX Honest 25g装", + "name_jp": "アムステルダマー・XXX オネスト(25g)", + "description": "带纸", + "price_jpy": 700, + "category": "Amsterdamer/阿姆斯特丹", + "price_cny": 33.6 + }, + { + "external_id": "2473", + "image_url": "https://ribenyan.com/data/images/goods/535_6903880d73b44.webp", + "name_cn": "Amsterdamer XXX Plain 25g装", + "name_jp": "アムステルダマー・XXX プレーン(25g)", + "description": "带纸", + "price_jpy": 700, + "category": "Amsterdamer/阿姆斯特丹", + "price_cny": 33.6 + }, + { + "external_id": "2529", + "image_url": "https://ribenyan.com/data/images/goods/540_690626d98ffaa.webp", + "name_cn": "老船长/Ark Royal Berry Rose Iced Tea Shag 30g装", + "name_jp": "アークローヤル・ベリーロゼアイスティー・シャグ(30g)", + "description": "带纸", + "price_jpy": 840, + "category": "Ark Royal/老船长", + "price_cny": 40.32 + }, + { + "external_id": "2527", + "image_url": "https://ribenyan.com/data/images/goods/541_690626d61fb34.webp", + "name_cn": "老船长/Ark Royal Berry Mix Ice Shag 30g装", + "name_jp": "アークローヤル・ベリーミックスアイス・シャグ(30g)", + "description": "带纸", + "price_jpy": 840, + "category": "Ark Royal/老船长", + "price_cny": 40.32 + }, + { + "external_id": "2106", + "image_url": "https://ribenyan.com/data/images/goods/539_690626de824a7.webp", + "name_cn": "老船长/Ark Royal Berry Rosetea Shag 30g装", + "name_jp": "アークローヤル・ベリーロゼティー・シャグ(30g)", + "description": "带纸", + "price_jpy": 840, + "category": "Ark Royal/老船长", + "price_cny": 40.32 + }, + { + "external_id": "3795", + "image_url": "https://ribenyan.com/data/images/goods/547_690626b941566.webp", + "name_cn": "老船长/Ark Royal Paradise Tea Shag 30g装", + "name_jp": "アークローヤル・パラダイスティー・シャグ(30g)", + "description": "带纸", + "price_jpy": 840, + "category": "Ark Royal/老船长", + "price_cny": 40.32 + }, + { + "external_id": "3797", + "image_url": "https://ribenyan.com/data/images/goods/538_690626e2c43cc.webp", + "name_cn": "老船长/Ark Royal Wineberry Shag 30g装", + "name_jp": "アークローヤル・ワインベリー・シャグ(30g)", + "description": "带纸", + "price_jpy": 840, + "category": "Ark Royal/老船长", + "price_cny": 40.32 + }, + { + "external_id": "3794", + "image_url": "https://ribenyan.com/data/images/goods/546_690626c94dbb5.webp", + "name_cn": "老船长/Ark Royal Vanilla Cream Shag 30g装", + "name_jp": "アークローヤル・バニラクリーム・シャグ(30g)", + "description": "带纸", + "price_jpy": 840, + "category": "Ark Royal/老船长", + "price_cny": 40.32 + }, + { + "external_id": "2093", + "image_url": "https://ribenyan.com/data/images/goods/545_690626cccbfba.webp", + "name_cn": "老船长/Ark Royal Original Shag 30g装", + "name_jp": "アークローヤル・オリジナル・シャグ(30g)", + "description": "带纸", + "price_jpy": 840, + "category": "Ark Royal/老船长", + "price_cny": 40.32 + }, + { + "external_id": "2104", + "image_url": "https://ribenyan.com/data/images/goods/549_690626c235f40.webp", + "name_cn": "老船长/Ark Royal Virginia Gold Shag 30g装", + "name_jp": "アークローヤル・バージニアゴールド・シャグ(30g)", + "description": "带纸", + "price_jpy": 880, + "category": "Ark Royal/老船长", + "price_cny": 42.24 + }, + { + "external_id": "3796", + "image_url": "https://ribenyan.com/data/images/goods/550_690626bd6b31c.webp", + "name_cn": "老船长/Ark Royal Pina Colada Shag 30g装", + "name_jp": "アークローヤル・ピニャコラーダ・シャグ(30g)", + "description": "带纸", + "price_jpy": 840, + "category": "Ark Royal/老船长", + "price_cny": 40.32 + }, + { + "external_id": "2091", + "image_url": "https://ribenyan.com/data/images/goods/544_690626d191070.webp", + "name_cn": "老船长/Ark Royal Ice Menthol Shag 30g装", + "name_jp": "アークローヤル・アイスメンソール・シャグ(30g)", + "description": "带纸", + "price_jpy": 840, + "category": "Ark Royal/老船长", + "price_cny": 40.32 + }, + { + "external_id": "2118", + "image_url": "https://ribenyan.com/data/images/goods/537_690626e792b24.webp", + "name_cn": "老船长/Ark Royal Latakia Shag 30g装", + "name_jp": "アークローヤル・ラタキア・シャグ(30g)", + "description": "带纸", + "price_jpy": 880, + "category": "Ark Royal/老船长", + "price_cny": 42.24 + }, + { + "external_id": "2101", + "image_url": "https://ribenyan.com/data/images/goods/548_690626c5d23ab.webp", + "name_cn": "老船长/Ark Royal Turkish Blend Shag 30g装", + "name_jp": "アークローヤル・ターキッシュブレンド・シャグ(30g)", + "description": "带纸", + "price_jpy": 880, + "category": "Ark Royal/老船长", + "price_cny": 42.24 + }, + { + "external_id": "4562", + "image_url": "https://ribenyan.com/data/images/goods/521_690b0fdad88a6.webp", + "name_cn": "Bali Shag Halfzwal 40g装", + "name_jp": "バリシャグ・ハーフスワレ(40g)", + "description": "带纸", + "price_jpy": 1210, + "category": "Bali Shag/巴厘岛", + "price_cny": 58.08 + }, + { + "external_id": "4563", + "image_url": "https://ribenyan.com/data/images/goods/520_690b11852a224.webp", + "name_cn": "Bali Shag Rounded Virginia 40g装", + "name_jp": "バリシャグ・ラウンデッド・バージニア(40g)", + "description": "带纸", + "price_jpy": 1210, + "category": "Bali Shag/巴厘岛", + "price_cny": 58.08 + }, + { + "external_id": "2549", + "image_url": "https://ribenyan.com/data/images/goods/518_690b12424bef2.webp", + "name_cn": "Bali Shag Rich Virginia 40g装", + "name_jp": "バリシャグ リッチバージニア(40g)", + "description": "带纸", + "price_jpy": 1210, + "category": "Bali Shag/巴厘岛", + "price_cny": 58.08 + }, + { + "external_id": "4565", + "image_url": "https://ribenyan.com/data/images/goods/466_690b102224c70.webp", + "name_cn": "Bali Shag Premium Virginia 40g装", + "name_jp": "バリシャグ・プレミアム・バージニア(40g)", + "description": "带纸", + "price_jpy": 1210, + "category": "Bali Shag/巴厘岛", + "price_cny": 58.08 + }, + { + "external_id": "T942", + "image_url": "https://ribenyan.com/data/images/goods/942_69aa66acacf5f.webp", + "name_cn": "Black Hawk Cappuccino 100g 罐装", + "name_jp": "ブラックホーク カプチーノ", + "description": "带纸", + "price_jpy": 3000, + "category": "Black Hawk/黑鹰", + "price_cny": 144.0 + }, + { + "external_id": "3724", + "image_url": "https://ribenyan.com/data/images/goods/639_690b389022ed5.webp", + "name_cn": "Black Spider Shag Coconut Milk 30g装", + "name_jp": "ブラックスパイダーシャグ・ ココナッツミルク", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "3727", + "image_url": "https://ribenyan.com/data/images/goods/643_690b3a0a13c53.webp", + "name_cn": "Black Spider Shag Chocolate 30g装", + "name_jp": "ブラックスパイダーシャグ・ チョコレート", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2859", + "image_url": "https://ribenyan.com/data/images/goods/934_69a58f82c6b72.webp", + "name_cn": "Black Spider Shag Melon Menthol 30g装", + "name_jp": "ブラックスパイダーシャグ・メロンメンソール", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "3723", + "image_url": "https://ribenyan.com/data/images/goods/635_690b2654adb03.webp", + "name_cn": "Black Spider Shag Cafe Vanilla 30g装", + "name_jp": "ブラックスパイダーシャグ・ カフェバニラ", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2858", + "image_url": "https://ribenyan.com/data/images/goods/935_69a58ff4d60cc.webp", + "name_cn": "Black Spider Shag Orange Peel Menthol 30g装", + "name_jp": "ブラックスパイダーシャグ・オレンジピールメンソール", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "3905", + "image_url": "https://ribenyan.com/data/images/goods/656_690b3e62c18b8.webp", + "name_cn": "Black Spider Shag Lemon Tea 30g装", + "name_jp": "ブラックスパイダーシャグ・ レモンティー", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2576", + "image_url": "https://ribenyan.com/data/images/goods/646_690b3b3f6491c.webp", + "name_cn": "Black Spider Shag Peach 30g装", + "name_jp": "ブラックスパイダーシャグ・ ピーチ", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "3721", + "image_url": "https://ribenyan.com/data/images/goods/630_69495673a31c6.webp", + "name_cn": "Black Spider Shag Ice Vanilla 30g装", + "name_jp": "ブラックスパイダーシャグ・アイスバニラ", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2813", + "image_url": "https://ribenyan.com/data/images/goods/654_690b3de8251b1.webp", + "name_cn": "Black Spider Shag Lychee Menthol 30g装", + "name_jp": "ブラックスパイダーシャグ・ ライチメンソール", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2791", + "image_url": "https://ribenyan.com/data/images/goods/637_690b26fb86e08.webp", + "name_cn": "Black Spider Shag Kyoho Menthol 30g装", + "name_jp": "ブラックスパイダーシャグ・ 巨峰メンソール", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2129", + "image_url": "https://ribenyan.com/data/images/goods/647_690b3b82e1c24.webp", + "name_cn": "Black Spider Shag Frozen Mojito 30g装", + "name_jp": "ブラックスパイダーシャグ・ フローズンモヒート", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2131", + "image_url": "https://ribenyan.com/data/images/goods/650_690b3c9ad90af.webp", + "name_cn": "Black Spider Shag Mango Lassi 30g装", + "name_jp": "ブラックスパイダーシャグ・ マンゴーラッシー", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "3726", + "image_url": "https://ribenyan.com/data/images/goods/642_690b39730e362.webp", + "name_cn": "Black Spider Shag Cherry Cola 30g装", + "name_jp": "ブラックスパイダーシャグ・ チェリーコーラ", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2132", + "image_url": "https://ribenyan.com/data/images/goods/653_690b3d7ee72bd.webp", + "name_cn": "Black Spider Shag Yuzu 30g装", + "name_jp": "ブラックスパイダーシャグ・ 柚子", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2587", + "image_url": "https://ribenyan.com/data/images/goods/652_690b3d31f08ea.webp", + "name_cn": "Black Spider Shag Menthol Max 30g装", + "name_jp": "ブラックスパイダーシャグ・ メンソール マックス", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2586", + "image_url": "https://ribenyan.com/data/images/goods/651_690b3cf105e85.webp", + "name_cn": "Black Spider Shag Mixed Nuts 30g装", + "name_jp": "ブラックスパイダーシャグ・ ミックスナッツ", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2575", + "image_url": "https://ribenyan.com/data/images/goods/645_690b3af452f02.webp", + "name_cn": "Black Spider Shag Virginia Plus 30g装", + "name_jp": "ブラックスパイダーシャグ・ バージニア プラス", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "3725", + "image_url": "https://ribenyan.com/data/images/goods/640_690b38de7ffae.webp", + "name_cn": "Black Spider Shag Ginger Ale 30g装", + "name_jp": "ブラックスパイダーシャグ・ ジンジャーエール", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2485", + "image_url": "https://ribenyan.com/data/images/goods/638_690b384b98ced.webp", + "name_cn": "Black Spider Shag Green Tea 30g装", + "name_jp": "ブラックスパイダーシャグ・ グリーンティー", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2792", + "image_url": "https://ribenyan.com/data/images/goods/641_690b392fe1905.webp", + "name_cn": "Black Spider Shag Double Rich Custard 30g装", + "name_jp": "ブラックスパイダーシャグ・ ダブルリッチカスタード", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2447", + "image_url": "https://ribenyan.com/data/images/goods/636_690b269de3bb1.webp", + "name_cn": "Black Spider Shag Kir Royale 30g装", + "name_jp": "ブラックスパイダーシャグ・ キールロワイヤル", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2574", + "image_url": "https://ribenyan.com/data/images/goods/634_690b2603730a2.webp", + "name_cn": "Black Spider Shag Ogura 30g装", + "name_jp": "ブラックスパイダーシャグ・ 小倉", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "3722", + "image_url": "https://ribenyan.com/data/images/goods/633_690b25ac5a03d.webp", + "name_cn": "Black Spider Shag Amazon Guarana 30g装", + "name_jp": "ブラックスパイダーシャグ・ アマゾンガラナ", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "3904", + "image_url": "https://ribenyan.com/data/images/goods/632_690b24fd094ba.webp", + "name_cn": "Black Spider Shag Afternoon Tea 30g装", + "name_jp": "ブラックスパイダーシャグ・ アフタヌーンティー", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2790", + "image_url": "https://ribenyan.com/data/images/goods/631_690b24517ce38.webp", + "name_cn": "Black Spider Shag Apple Pie 30g装", + "name_jp": "ブラックスパイダーシャグ・ アップルパイ", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2577", + "image_url": "https://ribenyan.com/data/images/goods/648_690b3bc7157f7.webp", + "name_cn": "Black Spider Shag Muscat 30g装", + "name_jp": "ブラックスパイダーシャグ・ マスカット", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2130", + "image_url": "https://ribenyan.com/data/images/goods/649_690b3c5bc837e.webp", + "name_cn": "Black Spider Shag Marron Glace 30g装", + "name_jp": "ブラックスパイダーシャグ・ マロングラッセ", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2133", + "image_url": "https://ribenyan.com/data/images/goods/655_690b3e27c7ec6.webp", + "name_cn": "Black Spider Shag Ramune 30g装", + "name_jp": "ブラックスパイダーシャグ・ ラムネ", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2857", + "image_url": "https://ribenyan.com/data/images/goods/933_69a58efd529d2.webp", + "name_cn": "Black Spider Shag Strawberry Candy 30g装", + "name_jp": "ブラックスパイダーシャグ・イチゴキャンディ", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "2812", + "image_url": "https://ribenyan.com/data/images/goods/644_690b3a9cd6295.webp", + "name_cn": "Black Spider Shag Tropical Menthol 30g装", + "name_jp": "ブラックスパイダーシャグ・ トロピカルメンソール", + "description": "", + "price_jpy": 830, + "category": "Black Spider/黑蜘蛛", + "price_cny": 39.84 + }, + { + "external_id": "3839", + "image_url": "https://ribenyan.com/data/images/goods/670_690b51879e3e3.webp", + "name_cn": "Black Devil Original Shag Coconut Milk 40g装", + "name_jp": "ブラックデビル・オリジナル・シャグ ココナッツミルク", + "description": "带纸", + "price_jpy": 1100, + "category": "BlackDevil/黑魔鬼", + "price_cny": 52.8 + }, + { + "external_id": "2612", + "image_url": "https://ribenyan.com/data/images/goods/680_690c0b3a7de04.webp", + "name_cn": "Blue Note Fine Cut 40g装", + "name_jp": "ブルーノート・ファインカット", + "description": "", + "price_jpy": 1500, + "category": "Blue Note/蓝色音符", + "price_cny": 72.0 + }, + { + "external_id": "3808", + "image_url": "https://ribenyan.com/data/images/goods/555_6906c255a9cf5.webp", + "name_cn": "骆驼/Camel Light 25g装", + "name_jp": "キャメル・ライト(25g)", + "description": "带纸", + "price_jpy": 770, + "category": "Camel/骆驼", + "price_cny": 36.96 + }, + { + "external_id": "3953", + "image_url": "https://ribenyan.com/data/images/goods/554_6906c25c990ad.webp", + "name_cn": "骆驼/Camel Menthol 25g装", + "name_jp": "キャメル・メンソール(25g)", + "description": "带纸", + "price_jpy": 770, + "category": "Camel/骆驼", + "price_cny": 36.96 + }, + { + "external_id": "3952", + "image_url": "https://ribenyan.com/data/images/goods/556_6906c24d64bb6.webp", + "name_cn": "骆驼/Camel Natural 25g装", + "name_jp": "キャメル・ナチュラル(25g)", + "description": "带纸", + "price_jpy": 790, + "category": "Camel/骆驼", + "price_cny": 37.92 + }, + { + "external_id": "3807", + "image_url": "https://ribenyan.com/data/images/goods/553_6906c241da81e.webp", + "name_cn": "骆驼/Camel Original 25g装", + "name_jp": "キャメル・オリジナル(25g)", + "description": "带纸", + "price_jpy": 770, + "category": "Camel/骆驼", + "price_cny": 36.96 + }, + { + "external_id": "3987", + "image_url": "https://ribenyan.com/data/images/goods/423_6906344c35a6d.webp", + "name_cn": "卡布奇诺/Cappuccino 巧克力 40g装", + "name_jp": "カプチーノ・チョコ", + "description": "带纸", + "price_jpy": 1260, + "category": "Cappuccino/卡布奇诺", + "price_cny": 60.48 + }, + { + "external_id": "3988", + "image_url": "https://ribenyan.com/data/images/goods/408_690634ed93465.webp", + "name_cn": "卡布奇诺/Cappuccino 香草 40g装", + "name_jp": "カプチーノ・バニラ", + "description": "带纸", + "price_jpy": 1260, + "category": "Cappuccino/卡布奇诺", + "price_cny": 60.48 + }, + { + "external_id": "3113", + "image_url": "https://ribenyan.com/data/images/goods/432_69063379a5de6.webp", + "name_cn": "卡布奇诺/Cappuccino 40g装", + "name_jp": "カプチーノ", + "description": "带纸", + "price_jpy": 1260, + "category": "Cappuccino/卡布奇诺", + "price_cny": 60.48 + }, + { + "external_id": "3736", + "image_url": "https://ribenyan.com/data/images/goods/118_690639680e57c.webp", + "name_cn": "黑船长/Captain Black Shag Bright Virginia 30g装", + "name_jp": "キャプテンブラック シャグブライトバージニア", + "description": "带纸", + "price_jpy": 920, + "category": "Captain Black/黑船长", + "price_cny": 44.16 + }, + { + "external_id": "3735", + "image_url": "https://ribenyan.com/data/images/goods/119_690638e2429e0.webp", + "name_cn": "黑船长/Captain Black Shag Vanilla 30g装", + "name_jp": "キャプテンブラック シャグバニラ", + "description": "带纸", + "price_jpy": 920, + "category": "Captain Black/黑船长", + "price_cny": 44.16 + }, + { + "external_id": "3734", + "image_url": "https://ribenyan.com/data/images/goods/123_69063827530a0.webp", + "name_cn": "黑船长/Captain Black Shag Cherry 30g装", + "name_jp": "キャプテンブラック シャグチェリー", + "description": "带纸", + "price_jpy": 920, + "category": "Captain Black/黑船长", + "price_cny": 44.16 + }, + { + "external_id": "4382", + "image_url": "https://ribenyan.com/data/images/goods/513_690af951193a1.webp", + "name_cn": "Che/切格瓦拉 25g装", + "name_jp": "チェ・シャグ(25g)", + "description": "带纸", + "price_jpy": 680, + "category": "Che/切格瓦拉", + "price_cny": 32.64 + }, + { + "external_id": "2584", + "image_url": "https://ribenyan.com/data/images/goods/523_690afa2961a1e.webp", + "name_cn": "Che/切格瓦拉 佛吉尼亚 25g装", + "name_jp": "チェ シャグ・バージニア(25g)", + "description": "带纸", + "price_jpy": 680, + "category": "Che/切格瓦拉", + "price_cny": 32.64 + }, + { + "external_id": "4486", + "image_url": "https://ribenyan.com/data/images/goods/486_690af98b355cf.webp", + "name_cn": "Che/切格瓦拉 银色 25g装", + "name_jp": "チェ・シャグ・シルバー(25g)", + "description": "带纸", + "price_jpy": 680, + "category": "Che/切格瓦拉", + "price_cny": 32.64 + }, + { + "external_id": "2092", + "image_url": "https://ribenyan.com/data/images/goods/484_690af9d6919c4.webp", + "name_cn": "Che/切格瓦拉 蓝色 25g装", + "name_jp": "チェ・シャグ・ブルー(25g)", + "description": "带纸", + "price_jpy": 680, + "category": "Che/切格瓦拉", + "price_cny": 32.64 + }, + { + "external_id": "2507", + "image_url": "https://ribenyan.com/data/images/goods/485_690afa0700eb4.webp", + "name_cn": "Che/切格瓦拉 黑薄荷 25g装", + "name_jp": "チェ・シャグ・ブラックメンソール(25g)", + "description": "带纸", + "price_jpy": 680, + "category": "Che/切格瓦拉", + "price_cny": 32.64 + }, + { + "external_id": "4383", + "image_url": "https://ribenyan.com/data/images/goods/514_690afa16917d5.webp", + "name_cn": "Che/切格瓦拉 薄荷 25g装", + "name_jp": "チェ・シャグ・メンソール(25g)", + "description": "带纸", + "price_jpy": 680, + "category": "Che/切格瓦拉", + "price_cny": 32.64 + }, + { + "external_id": "T941", + "image_url": "https://ribenyan.com/data/images/goods/941_69aa66ba929eb.webp", + "name_cn": "Che/切格瓦拉 Shag 100g 罐装", + "name_jp": "チェ シャグ100g缶", + "description": "带纸", + "price_jpy": 2650, + "category": "Che/切格瓦拉", + "price_cny": 127.2 + }, + { + "external_id": "2056", + "image_url": "https://ribenyan.com/data/images/goods/476_690affd626381.webp", + "name_cn": "Choice Double Grape 30g装", + "name_jp": "チョイス・ダブルグレープ(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "4510", + "image_url": "https://ribenyan.com/data/images/goods/515_690afd5ddd762.webp", + "name_cn": "Choice Double Apple 30g装", + "name_jp": "チョイス・ダブルアップル(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "2301", + "image_url": "https://ribenyan.com/data/images/goods/474_690afacf78203.webp", + "name_cn": "Choice Dolce Caramel Vanilla 30g装", + "name_jp": "チョイス・ドルチェ・キャラメルバニラ(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "4407", + "image_url": "https://ribenyan.com/data/images/goods/477_690affa97cc3a.webp", + "name_cn": "Choice Dark Chocolate 30g装", + "name_jp": "チョイス・ダークチョコレート(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "4549", + "image_url": "https://ribenyan.com/data/images/goods/524_690afae296dda.webp", + "name_cn": "Choice Double Vanilla 30g装", + "name_jp": "チョイス・ダブルバニラ(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "4512", + "image_url": "https://ribenyan.com/data/images/goods/481_690aff38048be.webp", + "name_cn": "Choice Cafe 30g装", + "name_jp": "チョイス・カフェ(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "4254", + "image_url": "https://ribenyan.com/data/images/goods/516_690afd38816be.webp", + "name_cn": "Choice Original 30g装", + "name_jp": "チョイス・オリジナル(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "4405", + "image_url": "https://ribenyan.com/data/images/goods/482_690afd87cb10b.webp", + "name_cn": "Choice Aromatic 30g装", + "name_jp": "チョイス・アロマティック(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "2071", + "image_url": "https://ribenyan.com/data/images/goods/517_690afafb51fd7.webp", + "name_cn": "Choice Double Menthol 30g装", + "name_jp": "チョイス・ダブルメンソール(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "4511", + "image_url": "https://ribenyan.com/data/images/goods/475_690affecc0bd3.webp", + "name_cn": "Choice Double Cherry 30g装", + "name_jp": "チョイス・ダブルチェリー(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "2373", + "image_url": "https://ribenyan.com/data/images/goods/471_690b0058b3924.webp", + "name_cn": "Choice Rhubarb 30g装", + "name_jp": "チョイス・ルバーブ(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "4255", + "image_url": "https://ribenyan.com/data/images/goods/479_690aff77c7c6d.webp", + "name_cn": "Choice Cool Mint 30g装", + "name_jp": "チョイス・クールミント(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "2372", + "image_url": "https://ribenyan.com/data/images/goods/480_690aff5c15ccf.webp", + "name_cn": "Choice Kir Royale 30g装", + "name_jp": "チョイス・キールロワイヤル(30g)", + "description": "", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "2684", + "image_url": "https://ribenyan.com/data/images/goods/473_690b000fb890f.webp", + "name_cn": "Choice Mango 30g装", + "name_jp": "チョイス・マンゴー(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "CHOICE", + "price_cny": 39.84 + }, + { + "external_id": "2428", + "image_url": "https://ribenyan.com/data/images/goods/509_69098bf04ecb2.webp", + "name_cn": "Colts Vanilla 30g装", + "name_jp": "コルツ ウ゛ァニラ(30g)", + "description": "带纸", + "price_jpy": 850, + "category": "COLTS", + "price_cny": 40.8 + }, + { + "external_id": "2533", + "image_url": "https://ribenyan.com/data/images/goods/506_69098c0652f24.webp", + "name_cn": "Colts Fruity Jasmine 30g装", + "name_jp": "コルツ フルーティージャスミン(30g)", + "description": "带纸", + "price_jpy": 850, + "category": "COLTS", + "price_cny": 40.8 + }, + { + "external_id": "2462", + "image_url": "https://ribenyan.com/data/images/goods/508_69098bf6f4156.webp", + "name_cn": "Colts Green Tea 30g装", + "name_jp": "コルツ グリーンティー(30g)", + "description": "带纸", + "price_jpy": 850, + "category": "COLTS", + "price_cny": 40.8 + }, + { + "external_id": "2463", + "image_url": "https://ribenyan.com/data/images/goods/598_69098ca836eed.webp", + "name_cn": "Colts Smooth Taste 30g装", + "name_jp": "コルツ スムーステイスト 30g", + "description": "带纸", + "price_jpy": 850, + "category": "COLTS", + "price_cny": 40.8 + }, + { + "external_id": "2505", + "image_url": "https://ribenyan.com/data/images/goods/505_69098c0cb0090.webp", + "name_cn": "Colts Lychee 30g装", + "name_jp": "コルツ ライチ(30g)", + "description": "带纸", + "price_jpy": 850, + "category": "COLTS", + "price_cny": 40.8 + }, + { + "external_id": "2503", + "image_url": "https://ribenyan.com/data/images/goods/510_69098be8ebb23.webp", + "name_cn": "Colts Apple & Cinnamon 30g装", + "name_jp": "コルツ アップル&シナモン(30g)", + "description": "带纸", + "price_jpy": 850, + "category": "COLTS", + "price_cny": 40.8 + }, + { + "external_id": "2429", + "image_url": "https://ribenyan.com/data/images/goods/512_69098bdb90833.webp", + "name_cn": "Colts Clear Menthol 30g装", + "name_jp": "コルツ クリアーメンソール(30g)", + "description": "带纸", + "price_jpy": 850, + "category": "COLTS", + "price_cny": 40.8 + }, + { + "external_id": "2598", + "image_url": "https://ribenyan.com/data/images/goods/507_69098bfe9877f.webp", + "name_cn": "Colts Cherry 30g装", + "name_jp": "コルツ チェリー(30g)", + "description": "带纸", + "price_jpy": 850, + "category": "COLTS", + "price_cny": 40.8 + }, + { + "external_id": "2464", + "image_url": "https://ribenyan.com/data/images/goods/504_69098c13b615c.webp", + "name_cn": "Colts 100% Natural 30g装", + "name_jp": "コルツ 100%ナチュラル(30g)", + "description": "带纸", + "price_jpy": 850, + "category": "COLTS", + "price_cny": 40.8 + }, + { + "external_id": "2635", + "image_url": "https://ribenyan.com/data/images/goods/690_690d48b6a86b9.webp", + "name_cn": "Dora Watermelon 30g装", + "name_jp": "ドーラ・ウォーターメロン", + "description": "带纸", + "price_jpy": 900, + "category": "DORA", + "price_cny": 43.2 + }, + { + "external_id": "2637", + "image_url": "https://ribenyan.com/data/images/goods/625_690b0a841d5a5.webp", + "name_cn": "Dora Peach Cream 30g装", + "name_jp": "ドーラ・ピーチクリーム", + "description": "带纸", + "price_jpy": 900, + "category": "DORA", + "price_cny": 43.2 + }, + { + "external_id": "2874", + "image_url": "https://ribenyan.com/data/images/goods/816_698c0f0c49c15.webp", + "name_cn": "Dora Acai 30g装", + "name_jp": "ドーラ・アサイー", + "description": "带纸", + "price_jpy": 900, + "category": "DORA", + "price_cny": 43.2 + }, + { + "external_id": "2876", + "image_url": "https://ribenyan.com/data/images/goods/817_698c10b6ea96e.webp", + "name_cn": "Dora Cherry 30g装", + "name_jp": "ドーラ・チェリー", + "description": "带纸", + "price_jpy": 900, + "category": "DORA", + "price_cny": 43.2 + }, + { + "external_id": "2873", + "image_url": "https://ribenyan.com/data/images/goods/815_698c0e07d21b5.webp", + "name_cn": "Dora Ice 30g装", + "name_jp": "ドーラ・アイス", + "description": "带纸", + "price_jpy": 900, + "category": "DORA", + "price_cny": 43.2 + }, + { + "external_id": "2636", + "image_url": "https://ribenyan.com/data/images/goods/624_690b0a276b58c.webp", + "name_cn": "Dora Walnut Cream 30g装", + "name_jp": "ドーラ・ウォールナットクリーム", + "description": "带纸", + "price_jpy": 900, + "category": "DORA", + "price_cny": 43.2 + }, + { + "external_id": "2875", + "image_url": "https://ribenyan.com/data/images/goods/818_698c1284194d1.webp", + "name_cn": "Dora Creme Brulee 30g装", + "name_jp": "ドーラ・クレームブリュレ", + "description": "带纸", + "price_jpy": 900, + "category": "DORA", + "price_cny": 43.2 + }, + { + "external_id": "2634", + "image_url": "https://ribenyan.com/data/images/goods/623_690b09457512a.webp", + "name_cn": "Dora Yellow Virginia 30g装", + "name_jp": "ドーラ・イエローバージニア", + "description": "带纸", + "price_jpy": 900, + "category": "DORA", + "price_cny": 43.2 + }, + { + "external_id": "2207", + "image_url": "https://ribenyan.com/data/images/goods/569_690b0ac445f04.webp", + "name_cn": "Drum 50g装", + "name_jp": "ドラム(50g)", + "description": "带纸", + "price_jpy": 1400, + "category": "DRUM", + "price_cny": 67.2 + }, + { + "external_id": "2184", + "image_url": "https://ribenyan.com/data/images/goods/674_690c08993fbf6.webp", + "name_cn": "Flandria Spanish Virginia 25g装", + "name_jp": "フランドリア・スパニッシュ バージニア", + "description": "带纸", + "price_jpy": 760, + "category": "Flandria/佛朗德里亚", + "price_cny": 36.48 + }, + { + "external_id": "2343", + "image_url": "https://ribenyan.com/data/images/goods/677_690c0a1f089ab.webp", + "name_cn": "Flandria Virginia 25g装", + "name_jp": "フランドリア・バージニア", + "description": "带纸", + "price_jpy": 760, + "category": "Flandria/佛朗德里亚", + "price_cny": 36.48 + }, + { + "external_id": "3011", + "image_url": "https://ribenyan.com/data/images/goods/672_690c055243bf8.webp", + "name_cn": "Flandria Original 25g装", + "name_jp": "フランドリア・オリジナル", + "description": "带纸", + "price_jpy": 760, + "category": "Flandria/佛朗德里亚", + "price_cny": 36.48 + }, + { + "external_id": "2344", + "image_url": "https://ribenyan.com/data/images/goods/678_690c0a5e84a7e.webp", + "name_cn": "Flandria Vanilla 25g装", + "name_jp": "フランドリア・バニラ", + "description": "带纸", + "price_jpy": 760, + "category": "Flandria/佛朗德里亚", + "price_cny": 36.48 + }, + { + "external_id": "2757", + "image_url": "https://ribenyan.com/data/images/goods/673_690c083476a96.webp", + "name_cn": "Flandria Gold 25g装", + "name_jp": "フランドリア・ゴールド", + "description": "带纸", + "price_jpy": 760, + "category": "Flandria/佛朗德里亚", + "price_cny": 36.48 + }, + { + "external_id": "2758", + "image_url": "https://ribenyan.com/data/images/goods/675_690c09998f355.webp", + "name_cn": "Flandria Silver 25g装", + "name_jp": "フランドリア・シルバー", + "description": "带纸", + "price_jpy": 760, + "category": "Flandria/佛朗德里亚", + "price_cny": 36.48 + }, + { + "external_id": "2185", + "image_url": "https://ribenyan.com/data/images/goods/676_690c09e038c40.webp", + "name_cn": "Flandria Natural 25g装", + "name_jp": "フランドリア・ナチュラル", + "description": "带纸", + "price_jpy": 760, + "category": "Flandria/佛朗德里亚", + "price_cny": 36.48 + }, + { + "external_id": "2186", + "image_url": "https://ribenyan.com/data/images/goods/679_690c0ab3caba8.webp", + "name_cn": "Flandria Black 25g装", + "name_jp": "フランドリア・ブラック", + "description": "带纸", + "price_jpy": 760, + "category": "Flandria/佛朗德里亚", + "price_cny": 36.48 + }, + { + "external_id": "3731", + "image_url": "https://ribenyan.com/data/images/goods/602_6909906b867da.webp", + "name_cn": "金色混合/Golden Blend's Virginia Green 30g装", + "name_jp": "ゴールデンブレンド・ バージニアグリーン", + "description": "带纸", + "price_jpy": 900, + "category": "Golden Blend's/金色混合", + "price_cny": 43.2 + }, + { + "external_id": "4596", + "image_url": "https://ribenyan.com/data/images/goods/600_69098e8e91519.webp", + "name_cn": "金色混合/Golden Blend's Aromatic Shag 30g装", + "name_jp": "ゴールデンブレンド・ アロマティック", + "description": "带纸", + "price_jpy": 900, + "category": "Golden Blend's/金色混合", + "price_cny": 43.2 + }, + { + "external_id": "4597", + "image_url": "https://ribenyan.com/data/images/goods/601_6909902022861.webp", + "name_cn": "金色混合/Golden Blend's Virginia 30g装", + "name_jp": "ゴールデンブレンド・ バージニア", + "description": "带纸", + "price_jpy": 900, + "category": "Golden Blend's/金色混合", + "price_cny": 43.2 + }, + { + "external_id": "2940", + "image_url": "https://ribenyan.com/data/images/goods/443_6906c61b77e26.webp", + "name_cn": "Golden Virginia 50g装", + "name_jp": "ゴールデン・バージニア(50g)", + "description": "", + "price_jpy": 1400, + "category": "Golden Virginia/金色佛吉尼亚", + "price_cny": 67.2 + }, + { + "external_id": "3757", + "image_url": "https://ribenyan.com/data/images/goods/628_690b0e70e8667.webp", + "name_cn": "Harvest Shag Vanilla 30g装", + "name_jp": "ハーベスト・シャグバニラ", + "description": "带纸", + "price_jpy": 790, + "category": "HARVEST", + "price_cny": 37.92 + }, + { + "external_id": "3756", + "image_url": "https://ribenyan.com/data/images/goods/627_690b0dc8812ab.webp", + "name_cn": "Harvest Shag Cherry 30g装", + "name_jp": "ハーベスト・シャグチェリー", + "description": "带纸", + "price_jpy": 790, + "category": "HARVEST", + "price_cny": 37.92 + }, + { + "external_id": "2406", + "image_url": "https://ribenyan.com/data/images/goods/468_690b0f7ae3035.webp", + "name_cn": "Hi-Tabaco Non-Additive Premium 30g装", + "name_jp": "ハイタバコ・ノンアディティブプレミアム(30g)", + "description": "带纸", + "price_jpy": 1080, + "category": "Hi-Tabaco", + "price_cny": 51.84 + }, + { + "external_id": "2618", + "image_url": "https://ribenyan.com/data/images/goods/467_690b0faf154fe.webp", + "name_cn": "Hi-Tabaco Virginia Gold 30g装", + "name_jp": "ハイタバコ・バージニアゴールド(30g)", + "description": "带纸", + "price_jpy": 1080, + "category": "Hi-Tabaco", + "price_cny": 51.84 + }, + { + "external_id": "2347", + "image_url": "https://ribenyan.com/data/images/goods/469_690b0f44c3abf.webp", + "name_cn": "Hi-Tabaco Dark Fire 30g装", + "name_jp": "ハイタバコ ダークファイア(30g)", + "description": "带纸", + "price_jpy": 940, + "category": "Hi-Tabaco", + "price_cny": 45.12 + }, + { + "external_id": "3889", + "image_url": "https://ribenyan.com/data/images/goods/608_690995ab8c507.webp", + "name_cn": "JBR Blueberry Mint 30g装", + "name_jp": "JBR・ブルーベリーミント", + "description": "带纸", + "price_jpy": 900, + "category": "JBR", + "price_cny": 43.2 + }, + { + "external_id": "3887", + "image_url": "https://ribenyan.com/data/images/goods/606_690995438bbc2.webp", + "name_cn": "JBR Two Apple 30g装", + "name_jp": "JBR・ツーアップル", + "description": "带纸", + "price_jpy": 900, + "category": "JBR", + "price_cny": 43.2 + }, + { + "external_id": "3888", + "image_url": "https://ribenyan.com/data/images/goods/607_6909957e9e267.webp", + "name_cn": "JBR Vanilla 30g装", + "name_jp": "JBR・バニラ", + "description": "带纸", + "price_jpy": 900, + "category": "JBR", + "price_cny": 43.2 + }, + { + "external_id": "2795", + "image_url": "https://ribenyan.com/data/images/goods/605_690994d8bb630.webp", + "name_cn": "JBR Grape 30g装", + "name_jp": "JBR グレープ", + "description": "带纸", + "price_jpy": 900, + "category": "JBR", + "price_cny": 43.2 + }, + { + "external_id": "2368", + "image_url": "https://ribenyan.com/data/images/goods/459_690c1751b64f5.webp", + "name_cn": "伐木工/Lumber Jack Original 25g装", + "name_jp": "ランバージャック・オリジナル(25g)", + "description": "", + "price_jpy": 700, + "category": "Lumber Jack/伐木工", + "price_cny": 33.6 + }, + { + "external_id": "2369", + "image_url": "https://ribenyan.com/data/images/goods/458_690c17f28ad98.webp", + "name_cn": "伐木工/Lumber Jack Light 25g装", + "name_jp": "ランバージャック・ライト(25g)", + "description": "", + "price_jpy": 700, + "category": "Lumber Jack/伐木工", + "price_cny": 33.6 + }, + { + "external_id": "4550", + "image_url": "https://ribenyan.com/data/images/goods/567_69063cc88f791.webp", + "name_cn": "马坝/Mac Baren Pure 30g装", + "name_jp": "マックバーレン・ピュア(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "Mac Baren/马坝", + "price_cny": 39.84 + }, + { + "external_id": "4277", + "image_url": "https://ribenyan.com/data/images/goods/557_69063cb6ac79b.webp", + "name_cn": "马坝/Mac Baren American Blend 30g装", + "name_jp": "マックバーレン・アメリカンブレンド(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "Mac Baren/马坝", + "price_cny": 39.84 + }, + { + "external_id": "4278", + "image_url": "https://ribenyan.com/data/images/goods/558_69063cc01812d.webp", + "name_cn": "马坝/Mac Baren Virginia Blend 30g装", + "name_jp": "マックバーレン・ヴァージニアブレンド(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "Mac Baren/马坝", + "price_cny": 39.84 + }, + { + "external_id": "4279", + "image_url": "https://ribenyan.com/data/images/goods/568_69063cd22e513.webp", + "name_cn": "马坝/Mac Baren Halfzwal 30g装", + "name_jp": "マックバーレン・ハーフスワレ(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "Mac Baren/马坝", + "price_cny": 39.84 + }, + { + "external_id": "3842", + "image_url": "https://ribenyan.com/data/images/goods/561_69495235a5779.webp", + "name_cn": "曼尼顿/Manitou Gold 30g装", + "name_jp": "マニトウ・ゴールド(30g)", + "description": "带纸", + "price_jpy": 910, + "category": "Manitou/曼尼顿", + "price_cny": 43.68 + }, + { + "external_id": "3843", + "image_url": "https://ribenyan.com/data/images/goods/560_68f09a6406fc4.webp", + "name_cn": "曼尼顿/Manitou Gold 80g罐装", + "name_jp": "マニトウ・ゴールド(80g)", + "description": "带纸", + "price_jpy": 2420, + "category": "Manitou/曼尼顿", + "price_cny": 116.16 + }, + { + "external_id": "3841", + "image_url": "https://ribenyan.com/data/images/goods/562_68f0fc8e578de.webp", + "name_cn": "曼尼顿/Manitou Organic 25g装", + "name_jp": "マニトウ・オーガニック(25g)", + "description": "带纸", + "price_jpy": 910, + "category": "Manitou/曼尼顿", + "price_cny": 43.68 + }, + { + "external_id": "2532", + "image_url": "https://ribenyan.com/data/images/goods/449_690631b893f75.webp", + "name_cn": "Old Holborn Original 25g装", + "name_jp": "オールドホルボーン・オリジナル(25g)", + "description": "带纸", + "price_jpy": 800, + "category": "Old Holborn/老霍本", + "price_cny": 38.4 + }, + { + "external_id": "2531", + "image_url": "https://ribenyan.com/data/images/goods/448_69493b657742f.webp", + "name_cn": "Old Holborn Yellow 25g装", + "name_jp": "オールドホルボーン・イエロー(25g)", + "description": "带纸", + "price_jpy": 800, + "category": "Old Holborn/老霍本", + "price_cny": 38.4 + }, + { + "external_id": "4430", + "image_url": "https://ribenyan.com/data/images/goods/522_690c0d9a880d9.webp", + "name_cn": "Pepe Rich Green 30g装", + "name_jp": "ペペ・リッチグリーン(30g)", + "description": "带纸", + "price_jpy": 800, + "category": "PEPE", + "price_cny": 38.4 + }, + { + "external_id": "3030", + "image_url": "https://ribenyan.com/data/images/goods/461_690c0db0eb7d1.webp", + "name_cn": "Pepe Organic Shag 30g装", + "name_jp": "ペペ・オーガニック・シャグ(30g)", + "description": "带纸", + "price_jpy": 860, + "category": "PEPE", + "price_cny": 41.28 + }, + { + "external_id": "4431", + "image_url": "https://ribenyan.com/data/images/goods/462_690c0d50806d0.webp", + "name_cn": "Pepe Easy Green 30g装", + "name_jp": "ペペ・イージーグリーン(30g)", + "description": "带纸", + "price_jpy": 800, + "category": "PEPE", + "price_cny": 38.4 + }, + { + "external_id": "3840", + "image_url": "https://ribenyan.com/data/images/goods/460_690c0dc59652e.webp", + "name_cn": "Pepe Dark Green 30g装", + "name_jp": "ペペ・ダークグリーン(30g)", + "description": "带纸", + "price_jpy": 800, + "category": "PEPE", + "price_cny": 38.4 + }, + { + "external_id": "2452", + "image_url": "https://ribenyan.com/data/images/goods/629_690b0ef221677.webp", + "name_cn": "Perfect Blend Fine Cut 40g装", + "name_jp": "パーフェクトブレンド・ ファインカット", + "description": "", + "price_jpy": 1410, + "category": "Perfect Blend/完美混合", + "price_cny": 67.68 + }, + { + "external_id": "4299", + "image_url": "https://ribenyan.com/data/images/goods/519_690b20d1b9b22.webp", + "name_cn": "Pueblo Natural Shag 30g装", + "name_jp": "プエブロ・ナチュラル・シャグ(30g)", + "description": "带纸", + "price_jpy": 900, + "category": "PUEBLO/普韦布洛", + "price_cny": 43.2 + }, + { + "external_id": "4440", + "image_url": "https://ribenyan.com/data/images/goods/463_690b215c6b721.webp", + "name_cn": "Pueblo Natural Shag Blue 30g装", + "name_jp": "プエブロ・ナチュラル・シャグ・ブルー(30g)", + "description": "带纸", + "price_jpy": 900, + "category": "PUEBLO/普韦布洛", + "price_cny": 43.2 + }, + { + "external_id": "2164", + "image_url": "https://ribenyan.com/data/images/goods/465_690b2111542b0.webp", + "name_cn": "Pueblo Natural Shag Orange 30g装", + "name_jp": "プエブロ・ナチュラル・シャグ・オレンジ(30g)", + "description": "带纸", + "price_jpy": 900, + "category": "PUEBLO/普韦布洛", + "price_cny": 43.2 + }, + { + "external_id": "2165", + "image_url": "https://ribenyan.com/data/images/goods/464_690b213444408.webp", + "name_cn": "Pueblo Natural Shag Pink 30g装", + "name_jp": "プエブロ・ナチュラル・シャグ・ピンク(30g)", + "description": "带纸", + "price_jpy": 900, + "category": "PUEBLO/普韦布洛", + "price_cny": 43.2 + }, + { + "external_id": "T583", + "image_url": "https://ribenyan.com/data/images/goods/583_68fce0c90568a.webp", + "name_cn": "R&W Classic 30g装", + "name_jp": "R&W・クラシック", + "description": "带纸", + "price_jpy": 1090, + "category": "R&W", + "price_cny": 52.32 + }, + { + "external_id": "T582", + "image_url": "https://ribenyan.com/data/images/goods/582_68fb9d5757453.webp", + "name_cn": "R&W Premium 30g装", + "name_jp": "R&W・プレミアム", + "description": "带纸", + "price_jpy": 1090, + "category": "R&W", + "price_cny": 52.32 + }, + { + "external_id": "T584", + "image_url": "https://ribenyan.com/data/images/goods/584_68fce12b60739.webp", + "name_cn": "R&W Gold 30g装", + "name_jp": "R&W・ゴールド", + "description": "带纸", + "price_jpy": 1090, + "category": "R&W", + "price_cny": 52.32 + }, + { + "external_id": "T581", + "image_url": "https://ribenyan.com/data/images/goods/581_68fb9cbf73a58.webp", + "name_cn": "R&W Green 30g装", + "name_jp": "R&W・グリーン", + "description": "带纸", + "price_jpy": 1090, + "category": "R&W", + "price_cny": 52.32 + }, + { + "external_id": "T926", + "image_url": "https://ribenyan.com/data/images/goods/926_6996d71993c3a.webp", + "name_cn": "R&W Classic Shag 100g Paper Can", + "name_jp": "RAW・クラシック・シャグ(100g・ペーパー缶)", + "description": "下单预定,订货大概5-8天", + "price_jpy": 3550, + "category": "R&W", + "price_cny": 170.4 + }, + { + "external_id": "T925", + "image_url": "https://ribenyan.com/data/images/goods/925_6996d70b6e43e.webp", + "name_cn": "R&W Premium 100g Paper Can", + "name_jp": "RAW・プレミアム・100g・ペーパー缶", + "description": "带纸", + "price_jpy": 3550, + "category": "R&W", + "price_cny": 170.4 + }, + { + "external_id": "T924", + "image_url": "https://ribenyan.com/data/images/goods/924_6996d8170df08.webp", + "name_cn": "R&W Green 100g Paper Can", + "name_jp": "RAW・グリーン・100g・ペーパー缶", + "description": "带纸", + "price_jpy": 3550, + "category": "R&W", + "price_cny": 170.4 + }, + { + "external_id": "2423", + "image_url": "https://ribenyan.com/data/images/goods/456_694a021508532.webp", + "name_cn": "红场/Red Field Honey 20g装", + "name_jp": "レッドフィールド・ハニー(20g)", + "description": "带纸", + "price_jpy": 560, + "category": "RED FiELD/红场", + "price_cny": 26.88 + }, + { + "external_id": "2209", + "image_url": "https://ribenyan.com/data/images/goods/451_690c18e29f266.webp", + "name_cn": "红场/Red Field Maple 20g装", + "name_jp": "レッドフィールド・メープル(20g)", + "description": "带纸", + "price_jpy": 560, + "category": "RED FiELD/红场", + "price_cny": 26.88 + }, + { + "external_id": "2422", + "image_url": "https://ribenyan.com/data/images/goods/453_690c18b1b94e1.webp", + "name_cn": "红场/Red Field Vanilla Caramel 20g装", + "name_jp": "レッドフィールド・バニラキャラメル(20g)", + "description": "带纸", + "price_jpy": 560, + "category": "RED FiELD/红场", + "price_cny": 26.88 + }, + { + "external_id": "2208", + "image_url": "https://ribenyan.com/data/images/goods/452_690c18c654696.webp", + "name_cn": "红场/Red Field Plum 20g装", + "name_jp": "レッドフィールド・プラム(20g)", + "description": "带纸", + "price_jpy": 560, + "category": "RED FiELD/红场", + "price_cny": 26.88 + }, + { + "external_id": "3920", + "image_url": "https://ribenyan.com/data/images/goods/454_690c1895ef580.webp", + "name_cn": "红场/Red Field Virginia 20g装", + "name_jp": "レッドフィールド・バージニア(20g)", + "description": "带纸", + "price_jpy": 560, + "category": "RED FiELD/红场", + "price_cny": 26.88 + }, + { + "external_id": "3919", + "image_url": "https://ribenyan.com/data/images/goods/457_690c1860d8e9b.webp", + "name_cn": "红场/Red Field Natural 20g装", + "name_jp": "レッドフィールド・ナチュラル(20g)", + "description": "带纸", + "price_jpy": 560, + "category": "RED FiELD/红场", + "price_cny": 26.88 + }, + { + "external_id": "3918", + "image_url": "https://ribenyan.com/data/images/goods/455_690c1883b47eb.webp", + "name_cn": "红场/Red Field Arctic Menthol 20g装", + "name_jp": "レッドフィールド・アークティックメンソール(20g)", + "description": "带纸", + "price_jpy": 560, + "category": "RED FiELD/红场", + "price_cny": 26.88 + }, + { + "external_id": "2869", + "image_url": "https://ribenyan.com/data/images/goods/487_690af75f157c3.webp", + "name_cn": "Spade New York Blend 20g装", + "name_jp": "スペード・ニューヨークブレンド(20g)", + "description": "带纸", + "price_jpy": 500, + "category": "SPADE", + "price_cny": 24.0 + }, + { + "external_id": "2446", + "image_url": "https://ribenyan.com/data/images/goods/488_690af72b3186a.webp", + "name_cn": "Spade Air Blend 20g装", + "name_jp": "スペード・エアーブレンド(20g)", + "description": "带纸", + "price_jpy": 500, + "category": "SPADE", + "price_cny": 24.0 + }, + { + "external_id": "2732", + "image_url": "https://ribenyan.com/data/images/goods/617_690af1c37cb1a.webp", + "name_cn": "Stanley Double Chocolate 25g装", + "name_jp": "斯坦利/スタンレー・ダブル・ チョコレート", + "description": "带纸", + "price_jpy": 720, + "category": "STANLEY/斯坦利", + "price_cny": 34.56 + }, + { + "external_id": "2745", + "image_url": "https://ribenyan.com/data/images/goods/618_690af23803ca1.webp", + "name_cn": "Stanley Double Vanilla 25g装", + "name_jp": "斯坦利/スタンレー・ダブル・ バニラ", + "description": "带纸", + "price_jpy": 720, + "category": "STANLEY/斯坦利", + "price_cny": 34.56 + }, + { + "external_id": "2726", + "image_url": "https://ribenyan.com/data/images/goods/613_690aeea8a5a3b.webp", + "name_cn": "Stanley Double Apple 25g装", + "name_jp": "斯坦利/スタンレー・ダブル・ アップル", + "description": "带纸", + "price_jpy": 720, + "category": "STANLEY/斯坦利", + "price_cny": 34.56 + }, + { + "external_id": "2727", + "image_url": "https://ribenyan.com/data/images/goods/614_690af03031501.webp", + "name_cn": "Stanley Double Orange 25g装", + "name_jp": "斯坦利/スタンレー・ダブル・ オレンジ", + "description": "带纸", + "price_jpy": 720, + "category": "STANLEY/斯坦利", + "price_cny": 34.56 + }, + { + "external_id": "2730", + "image_url": "https://ribenyan.com/data/images/goods/615_690af0f1b12bd.webp", + "name_cn": "Stanley Double Cassis 25g装", + "name_jp": "斯坦利/スタンレー・ダブル・ カシス", + "description": "带纸", + "price_jpy": 720, + "category": "STANLEY/斯坦利", + "price_cny": 34.56 + }, + { + "external_id": "2720", + "image_url": "https://ribenyan.com/data/images/goods/612_690aec64cc178.webp", + "name_cn": "Stanley Double Ice Mint 25g装", + "name_jp": "スタンレー・ダブル・ アイスミント", + "description": "带纸", + "price_jpy": 720, + "category": "STANLEY/斯坦利", + "price_cny": 34.56 + }, + { + "external_id": "2138", + "image_url": "https://ribenyan.com/data/images/goods/490_6909a3312b96d.webp", + "name_cn": "Stanley Liquorice 30g装", + "name_jp": "スタンレー・リコリス(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "STANLEY/斯坦利", + "price_cny": 39.84 + }, + { + "external_id": "3838", + "image_url": "https://ribenyan.com/data/images/goods/492_6909a3224f6df.webp", + "name_cn": "Stanley Hazelnut 30g装", + "name_jp": "スタンレー・ヘーゼルナッツ(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "STANLEY/斯坦利", + "price_cny": 39.84 + }, + { + "external_id": "2719", + "image_url": "https://ribenyan.com/data/images/goods/611_6909a22797ec3.webp", + "name_cn": "Stanley Double Earl Grey Tea 25g装", + "name_jp": "スタンレー・ダブル・ アールグレイティー", + "description": "带纸", + "price_jpy": 720, + "category": "STANLEY/斯坦利", + "price_cny": 34.56 + }, + { + "external_id": "2731", + "image_url": "https://ribenyan.com/data/images/goods/616_690af15cefb11.webp", + "name_cn": "Stanley Double Coffee 25g装", + "name_jp": "斯坦利/スタンレー・ダブル・ コーヒー", + "description": "带纸", + "price_jpy": 720, + "category": "STANLEY/斯坦利", + "price_cny": 34.56 + }, + { + "external_id": "2747", + "image_url": "https://ribenyan.com/data/images/goods/619_690af29f4415c.webp", + "name_cn": "Stanley Double Hazelnut 25g装", + "name_jp": "斯坦利/スタンレー・ダブル・ ヘーゼルナッツ", + "description": "带纸", + "price_jpy": 720, + "category": "STANLEY/斯坦利", + "price_cny": 34.56 + }, + { + "external_id": "3479", + "image_url": "https://ribenyan.com/data/images/goods/493_6909a28f52a2b.webp", + "name_cn": "Stanley Banana 30g装", + "name_jp": "スタンレー・バナナ(30g)", + "description": "带纸", + "price_jpy": 830, + "category": "STANLEY/斯坦利", + "price_cny": 39.84 + }, + { + "external_id": "2749", + "image_url": "https://ribenyan.com/data/images/goods/621_690af3a0a7b04.webp", + "name_cn": "Stanley Double Licorice 25g装", + "name_jp": "斯坦利/スタンレー・ダブル・ リコリス", + "description": "带纸", + "price_jpy": 720, + "category": "STANLEY/斯坦利", + "price_cny": 34.56 + }, + { + "external_id": "2748", + "image_url": "https://ribenyan.com/data/images/goods/620_690af3347dce7.webp", + "name_cn": "Stanley Double Raspberry 25g装", + "name_jp": "斯坦利/スタンレー・ダブル・ ラズベリー", + "description": "带纸", + "price_jpy": 720, + "category": "STANLEY/斯坦利", + "price_cny": 34.56 + }, + { + "external_id": "T590", + "image_url": "https://ribenyan.com/data/images/goods/590_69062af28f79b.webp", + "name_cn": "三只狗/Three Dogs Perique Blend 30g装", + "name_jp": "スリードッグ・ペリクブレンド", + "description": "", + "price_jpy": 1000, + "category": "Three Dogs/三只狗", + "price_cny": 48.0 + }, + { + "external_id": "T589", + "image_url": "https://ribenyan.com/data/images/goods/589_69062a50c01b7.webp", + "name_cn": "三只狗/Three Dogs Madagascar Vanilla 30g装", + "name_jp": "スリードッグ・マダガスカルバニラ", + "description": "", + "price_jpy": 1000, + "category": "Three Dogs/三只狗", + "price_cny": 48.0 + }, + { + "external_id": "T382", + "image_url": "https://ribenyan.com/data/images/goods/382_69062cb953666.webp", + "name_cn": "三只狗/Three Dogs Cigar Blend 30g装", + "name_jp": "スリードッグ・シガーブレンド", + "description": "", + "price_jpy": 1000, + "category": "Three Dogs/三只狗", + "price_cny": 48.0 + }, + { + "external_id": "T440", + "image_url": "https://ribenyan.com/data/images/goods/440_690630b9507df.webp", + "name_cn": "三只狗/Three Dogs Cavendish Blend 30g装", + "name_jp": "スリードッグ・キャベンディッシュブレンド", + "description": "", + "price_jpy": 1000, + "category": "Three Dogs/三只狗", + "price_cny": 48.0 + }, + { + "external_id": "T588", + "image_url": "https://ribenyan.com/data/images/goods/588_690629ac963d9.webp", + "name_cn": "三只狗/Three Dogs Latakia Blend 30g装", + "name_jp": "アンホーラ・イングリッシュブレンド", + "description": "", + "price_jpy": 1000, + "category": "Three Dogs/三只狗", + "price_cny": 48.0 + }, + { + "external_id": "T434", + "image_url": "https://ribenyan.com/data/images/goods/434_69062f90b583f.webp", + "name_cn": "三只狗/Three Dogs Kretek Blend 30g装", + "name_jp": "スリードッグ・クレテックブレンド", + "description": "", + "price_jpy": 1000, + "category": "Three Dogs/三只狗", + "price_cny": 48.0 + }, + { + "external_id": "T591", + "image_url": "https://ribenyan.com/data/images/goods/591_69062bcd284f8.webp", + "name_cn": "三只狗/Three Dogs Turkish Blend 30g装", + "name_jp": "スリードッグ・ターキッシュブレンド", + "description": "", + "price_jpy": 1000, + "category": "Three Dogs/三只狗", + "price_cny": 48.0 + }, + { + "external_id": "3846", + "image_url": "https://ribenyan.com/data/images/goods/687_690c3039585f1.webp", + "name_cn": "Wild Bison 35g装", + "name_jp": "ワイルド・バイソン(35)", + "description": "", + "price_jpy": 1000, + "category": "Wild Bison", + "price_cny": 48.0 + }, + { + "external_id": "3847", + "image_url": "https://ribenyan.com/data/images/goods/688_690c3093742d9.webp", + "name_cn": "Wild Bison 150g装", + "name_jp": "ワ イ ル ド・バ イ ソ ン( 1 5 0 )", + "description": "", + "price_jpy": 4200, + "category": "Wild Bison", + "price_cny": 201.6 + }, + { + "external_id": "2786", + "image_url": "https://ribenyan.com/data/images/goods/503_6909972ba7c3b.webp", + "name_cn": "ZIG-ZAG Golden Shag 25g装", + "name_jp": "ジグザグ・ゴールデンシャグ(25g)", + "description": "", + "price_jpy": 680, + "category": "ZIG-ZAG", + "price_cny": 32.64 + }, + { + "external_id": "2787", + "image_url": "https://ribenyan.com/data/images/goods/502_690997270036f.webp", + "name_cn": "ZIG-ZAG Mint Shag 25g装", + "name_jp": "ジグザグ・ミントシャグ(25g)", + "description": "", + "price_jpy": 680, + "category": "ZIG-ZAG", + "price_cny": 32.64 + }, + { + "external_id": "4245", + "image_url": "https://ribenyan.com/data/images/goods/446_690af83a83a27.webp", + "name_cn": "Seven Seas Cherry 40g装", + "name_jp": "セブンシーズ・チェリー(40g)", + "description": "", + "price_jpy": 1250, + "category": "7seas/Seven Seas", + "price_cny": 60.0 + }, + { + "external_id": "4154", + "image_url": "https://ribenyan.com/data/images/goods/445_690af8789cbc0.webp", + "name_cn": "Seven Seas Regular Blend 40g装", + "name_jp": "セブンシーズ・レギュラーブレンド(40g)", + "description": "", + "price_jpy": 1250, + "category": "7seas/Seven Seas", + "price_cny": 60.0 + }, + { + "external_id": "4155", + "image_url": "https://ribenyan.com/data/images/goods/444_690af8a908145.webp", + "name_cn": "Seven Seas Royal Blend 40g装", + "name_jp": "セブンシーズ・ロイヤルブレンド(40g)", + "description": "", + "price_jpy": 1250, + "category": "7seas/Seven Seas", + "price_cny": 60.0 + }, + { + "external_id": "4153", + "image_url": "https://ribenyan.com/data/images/goods/447_690af8108c682.webp", + "name_cn": "Seven Seas Gold Blend 40g装", + "name_jp": "セブンシーズ・ゴールドブレンド(40g)", + "description": "", + "price_jpy": 1250, + "category": "7seas/Seven Seas", + "price_cny": 60.0 + }, + { + "external_id": "2169", + "image_url": "https://ribenyan.com/data/images/goods/525_690625a011eb1.webp", + "name_cn": "Amphora Rich Aroma 50g装", + "name_jp": "アンホーラ・リッチアロマ(50g)", + "description": "", + "price_jpy": 1490, + "category": "AMPHORA", + "price_cny": 71.52 + }, + { + "external_id": "2033", + "image_url": "https://ribenyan.com/data/images/goods/526_690625a997f85.webp", + "name_cn": "Amphora Full Aromatek 50g装", + "name_jp": "アンホーラ・フル・アロマテック(50g)", + "description": "", + "price_jpy": 1490, + "category": "AMPHORA", + "price_cny": 71.52 + }, + { + "external_id": "2168", + "image_url": "https://ribenyan.com/data/images/goods/527_6906259ad6046.webp", + "name_cn": "Amphora Black Cavendish 50g装", + "name_jp": "アンホーラ・ブラック・キャベンディッシュ(50g)", + "description": "", + "price_jpy": 1490, + "category": "AMPHORA", + "price_cny": 71.52 + }, + { + "external_id": "2652", + "image_url": "https://ribenyan.com/data/images/goods/528_690625b67e055.webp", + "name_cn": "Amphora English Blend 50g装", + "name_jp": "アンホーラ・イングリッシュブレンド(50g)", + "description": "", + "price_jpy": 1490, + "category": "AMPHORA", + "price_cny": 71.52 + }, + { + "external_id": "4253", + "image_url": "https://ribenyan.com/data/images/goods/552_690626b4821c5.webp", + "name_cn": "老船长/Ark Royal Full Aroma Pipe 30g装", + "name_jp": "アークローヤル・フルアロマパイプ(50g)", + "description": "", + "price_jpy": 1470, + "category": "Ark Royal/老船长", + "price_cny": 70.56 + }, + { + "external_id": "2648", + "image_url": "https://ribenyan.com/data/images/goods/559_690c0bc1830f6.webp", + "name_cn": "Blue Note 50g装", + "name_jp": "ブルーノート(50g)", + "description": "", + "price_jpy": 2300, + "category": "Blue Note/蓝色音符", + "price_cny": 110.4 + }, + { + "external_id": "4303", + "image_url": "https://ribenyan.com/data/images/goods/683_690c12dc3b777.webp", + "name_cn": "BORKUM RIFF Cherry Cavendish 50g装", + "name_jp": "ボルクムリーフ・チェリー キャベンディッシュ", + "description": "", + "price_jpy": 1590, + "category": "BORKUM RIFF", + "price_cny": 76.32 + }, + { + "external_id": "4305", + "image_url": "https://ribenyan.com/data/images/goods/684_690c134bd074d.webp", + "name_cn": "BORKUM RIFF Vanilla 50g装", + "name_jp": "ボルクムリーフ・バニラ", + "description": "", + "price_jpy": 1590, + "category": "BORKUM RIFF", + "price_cny": 76.32 + }, + { + "external_id": "4300", + "image_url": "https://ribenyan.com/data/images/goods/681_690c1021e54f2.webp", + "name_cn": "BORKUM RIFF 50g装", + "name_jp": "ボルクムリーフ", + "description": "", + "price_jpy": 1590, + "category": "BORKUM RIFF", + "price_cny": 76.32 + }, + { + "external_id": "4302", + "image_url": "https://ribenyan.com/data/images/goods/682_690c126f797ab.webp", + "name_cn": "BORKUM RIFF Original (原 Ultra Light) 50g装", + "name_jp": "ボルクムリーフ・オリジナル (旧ウルトラ・ライト)", + "description": "", + "price_jpy": 1590, + "category": "BORKUM RIFF", + "price_cny": 76.32 + }, + { + "external_id": "2107", + "image_url": "https://ribenyan.com/data/images/goods/592_6906259033c02.webp", + "name_cn": "Capito Mellow Light 40g装", + "name_jp": "カピート・メローライト(40)", + "description": "", + "price_jpy": 1350, + "category": "CAPITO", + "price_cny": 64.8 + }, + { + "external_id": "3975", + "image_url": "https://ribenyan.com/data/images/goods/593_69063a61e1226.webp", + "name_cn": "黑船长/Captain Black Original 42.5g装", + "name_jp": "キャプテンブラック オリジナル 42.5", + "description": "", + "price_jpy": 1400, + "category": "Captain Black/黑船长", + "price_cny": 67.2 + }, + { + "external_id": "3976", + "image_url": "https://ribenyan.com/data/images/goods/594_6949572e7cdfe.webp", + "name_cn": "黑船长/Captain Black Gold 42.5g装", + "name_jp": "キャプテンブラック ゴールド 42.5", + "description": "", + "price_jpy": 1400, + "category": "Captain Black/黑船长", + "price_cny": 67.2 + }, + { + "external_id": "3977", + "image_url": "https://ribenyan.com/data/images/goods/596_6906fca412d00.webp", + "name_cn": "黑船长/Captain Black Pipe Cherry 42.5g装", + "name_jp": "キャプテンブラック パイプチェリー", + "description": "", + "price_jpy": 1400, + "category": "Captain Black/黑船长", + "price_cny": 67.2 + }, + { + "external_id": "2425", + "image_url": "https://ribenyan.com/data/images/goods/715_690dc02af37b7.webp", + "name_cn": "黑船长/Captain Black Pipe Watermelon 42.5g装", + "name_jp": "キャプテンブラック パイプ ウォーターメロン 42.5", + "description": "", + "price_jpy": 1400, + "category": "Captain Black/黑船长", + "price_cny": 67.2 + }, + { + "external_id": "2426", + "image_url": "https://ribenyan.com/data/images/goods/595_6906fc05a9f72.webp", + "name_cn": "黑船长/Captain Black Pipe Green Apple 42.5g装", + "name_jp": "キャプテンブラック パイプ グリーンアップル 42.5", + "description": "", + "price_jpy": 1400, + "category": "Captain Black/黑船长", + "price_cny": 67.2 + }, + { + "external_id": "3978", + "image_url": "https://ribenyan.com/data/images/goods/597_6906fce15080b.webp", + "name_cn": "黑船长/Captain Black Pipe Royal 42.5g装", + "name_jp": "キャプテンブラック パイプロイヤル", + "description": "", + "price_jpy": 1400, + "category": "Captain Black/黑船长", + "price_cny": 67.2 + }, + { + "external_id": "3116", + "image_url": "https://ribenyan.com/data/images/goods/603_694a51441f0f6.webp", + "name_cn": "金色混合/Golden Blend's Vanilla 50g装", + "name_jp": "ゴールデンブレンド・バニ", + "description": "", + "price_jpy": 1650, + "category": "Golden Blend's/金色混合", + "price_cny": 79.2 + }, + { + "external_id": "3117", + "image_url": "https://ribenyan.com/data/images/goods/604_690990fd8bbb1.webp", + "name_cn": "金色混合/Golden Blend's Black Cherry 50g装", + "name_jp": "ゴールデンブレンド・ ブラックチェリー", + "description": "", + "price_jpy": 1650, + "category": "Golden Blend's/金色混合", + "price_cny": 79.2 + }, + { + "external_id": "3114", + "image_url": "https://ribenyan.com/data/images/goods/599_69098d0c60f62.webp", + "name_cn": "金色混合/Golden Blend's Amaretto 50g装", + "name_jp": "ゴールデンブレンド・アマレット", + "description": "", + "price_jpy": 1650, + "category": "Golden Blend's/金色混合", + "price_cny": 79.2 + }, + { + "external_id": "2037", + "image_url": "https://ribenyan.com/data/images/goods/626_690b0c9766b84.webp", + "name_cn": "Half and Half", + "name_jp": "ハーフ・アンド・ハーフ", + "description": "", + "price_jpy": 1620, + "category": "HALF AND HALF", + "price_cny": 77.76 + }, + { + "external_id": "3004", + "image_url": "https://ribenyan.com/data/images/goods/565_6906243471ff6.webp", + "name_cn": "马坝/Mac Baren Vanilla Cream 50g装", + "name_jp": "マックバレン・バニラ・クリーム(50g)", + "description": "", + "price_jpy": 1600, + "category": "Mac Baren/马坝", + "price_cny": 76.8 + }, + { + "external_id": "4134", + "image_url": "https://ribenyan.com/data/images/goods/566_6906243b3c397.webp", + "name_cn": "马坝/Mac Baren Virginia No.1 50g装", + "name_jp": "マックバレン・ヴァージニア・No.1(50g)", + "description": "", + "price_jpy": 1600, + "category": "Mac Baren/马坝", + "price_cny": 76.8 + }, + { + "external_id": "3005", + "image_url": "https://ribenyan.com/data/images/goods/564_690623f86401d.webp", + "name_cn": "马坝/Mac Baren Mixture 50g装", + "name_jp": "マックバレン・ミックスチュア(50g)", + "description": "", + "price_jpy": 1600, + "category": "Mac Baren/马坝", + "price_cny": 76.8 + }, + { + "external_id": "2467", + "image_url": "https://ribenyan.com/data/images/goods/718_690ef720d20f5.webp", + "name_cn": "桃山/Momoyama 100g装", + "name_jp": "桃山(100)", + "description": "", + "price_jpy": 3400, + "category": "Momoyama/桃山", + "price_cny": 163.2 + }, + { + "external_id": "2115", + "image_url": "https://ribenyan.com/data/images/goods/717_690ef683acc77.webp", + "name_cn": "桃山/Momoyama 40g装", + "name_jp": "桃山(40)", + "description": "", + "price_jpy": 1350, + "category": "Momoyama/桃山", + "price_cny": 64.8 + }, + { + "external_id": "3898", + "image_url": "https://ribenyan.com/data/images/goods/665_690b479c031f9.webp", + "name_cn": "Peterson My Mixture 965 50g装", + "name_jp": "ピーターソン マイミクスチャー965", + "description": "", + "price_jpy": 2600, + "category": "Peterson/彼得森", + "price_cny": 124.8 + }, + { + "external_id": "2161", + "image_url": "https://ribenyan.com/data/images/goods/659_690b4436a7106.webp", + "name_cn": "Peterson Aran 40g装", + "name_jp": "ピーターソン・アラン", + "description": "", + "price_jpy": 1520, + "category": "Peterson/彼得森", + "price_cny": 72.96 + }, + { + "external_id": "2162", + "image_url": "https://ribenyan.com/data/images/goods/671_690c024360595.webp", + "name_cn": "Peterson Connoisseur's Choice Black", + "name_jp": "ピーターソン・ コーンメイラブラック", + "description": "", + "price_jpy": 1520, + "category": "Peterson/彼得森", + "price_cny": 72.96 + }, + { + "external_id": "2160", + "image_url": "https://ribenyan.com/data/images/goods/658_690b43ea9c1a4.webp", + "name_cn": "Peterson Irish Dew 40g装", + "name_jp": "ピーターソン・ アイリッシュデュー", + "description": "", + "price_jpy": 1520, + "category": "Peterson/彼得森", + "price_cny": 72.96 + }, + { + "external_id": "2163", + "image_url": "https://ribenyan.com/data/images/goods/667_690b48348e6a6.webp", + "name_cn": "Peterson Wild Atlantic 40g装", + "name_jp": "ピーターソン・ワイルド アトランティック", + "description": "", + "price_jpy": 1520, + "category": "Peterson/彼得森", + "price_cny": 72.96 + }, + { + "external_id": "2559", + "image_url": "https://ribenyan.com/data/images/goods/660_690b44df0cb1a.webp", + "name_cn": "Peterson Elizabethan Mixture 50g装", + "name_jp": "ピーターソン エリザベシアンミクスチュア", + "description": "", + "price_jpy": 2600, + "category": "Peterson/彼得森", + "price_cny": 124.8 + }, + { + "external_id": "2112", + "image_url": "https://ribenyan.com/data/images/goods/716_690ed2a6005fb.webp", + "name_cn": "Promenade 40g装", + "name_jp": "プロムナード(40)", + "description": "", + "price_jpy": 1350, + "category": "PROMENADE", + "price_cny": 64.8 + }, + { + "external_id": "2119", + "image_url": "https://ribenyan.com/data/images/goods/722_690efb470d967.webp", + "name_cn": "ROCK'N CHAIR 40g装", + "name_jp": "ロックンチェア(40)", + "description": "", + "price_jpy": 1350, + "category": "ROCK'N CHAIR", + "price_cny": 64.8 + }, + { + "external_id": "2108", + "image_url": "https://ribenyan.com/data/images/goods/609_69099c0e943a1.webp", + "name_cn": "SILK ROAD 40g装", + "name_jp": "シルクロード(40)", + "description": "", + "price_jpy": 1350, + "category": "SILK ROAD/丝绸之路", + "price_cny": 64.8 + }, + { + "external_id": "2701", + "image_url": "https://ribenyan.com/data/images/goods/586_690625731ebec.webp", + "name_cn": "SOL Gold 80g装", + "name_jp": "SOL・ゴールド 80", + "description": "", + "price_jpy": 2480, + "category": "SOL", + "price_cny": 119.04 + }, + { + "external_id": "2700", + "image_url": "https://ribenyan.com/data/images/goods/585_690625797388c.webp", + "name_cn": "SOL Gold 30g装", + "name_jp": "SOL・ゴールド 30", + "description": "", + "price_jpy": 960, + "category": "SOL", + "price_cny": 46.08 + }, + { + "external_id": "2702", + "image_url": "https://ribenyan.com/data/images/goods/587_6906256d12f52.webp", + "name_cn": "SOL Mint 30g装", + "name_jp": "SOL・ミント 30", + "description": "", + "price_jpy": 960, + "category": "SOL", + "price_cny": 46.08 + }, + { + "external_id": "3001", + "image_url": "https://ribenyan.com/data/images/goods/610_6909a024e1603.webp", + "name_cn": "Sweet Dublin 50g装", + "name_jp": "スイート・ダブリン", + "description": "", + "price_jpy": 1620, + "category": "Sweet Dublin/甜都柏林", + "price_cny": 77.76 + }, + { + "external_id": "2614", + "image_url": "https://ribenyan.com/data/images/goods/622_690b06e5a908c.webp", + "name_cn": "Take Your Time 50g装", + "name_jp": "テイクユア・タイム", + "description": "", + "price_jpy": 2300, + "category": "Take Your Time", + "price_cny": 110.4 + }, + { + "external_id": "4093", + "image_url": "https://ribenyan.com/data/images/goods/719_690ef7dd363a5.webp", + "name_cn": "Larsen The Master's Blend 50g装", + "name_jp": "ラールセン・ザ・ マスターズ・ブレンド", + "description": "", + "price_jpy": 1690, + "category": "W.Ø.LARSEN", + "price_cny": 81.12 + }, + { + "external_id": "4369", + "image_url": "https://ribenyan.com/data/images/goods/721_690ef8d6e10a7.webp", + "name_cn": "Larsen Mellow and Tasty 50g装", + "name_jp": "ラールセン・ メロウ・アンド・テイスティー", + "description": "", + "price_jpy": 1690, + "category": "W.Ø.LARSEN", + "price_cny": 81.12 + }, + { + "external_id": "4094", + "image_url": "https://ribenyan.com/data/images/goods/720_690ef86a3c2cd.webp", + "name_cn": "Larsen Fine and Elegant 50g装", + "name_jp": "ラールセン・ ファイン・アンド・エレガント", + "description": "", + "price_jpy": 1690, + "category": "W.Ø.LARSEN", + "price_cny": 81.12 + }, + { + "external_id": "T936", + "image_url": "https://ribenyan.com/data/images/goods/936_69b91d8ce3ee7.webp", + "name_cn": "浅草柘制作所特选 辉黄色种极上天", + "name_jp": "浅草柘製作所特選 輝黃色種極上天", + "description": "", + "price_jpy": 4900, + "category": "浅草", + "price_cny": 235.2 + }, + { + "external_id": "2466", + "image_url": "https://ribenyan.com/data/images/goods/450_69062595c33c3.webp", + "name_cn": "飞鸟 100g罐装", + "name_jp": "飛鳥(100g・缶)", + "description": "", + "price_jpy": 3600, + "category": "飞鸟", + "price_cny": 172.8 + }, + { + "external_id": "2081", + "image_url": "https://ribenyan.com/data/images/goods/726_6913f3275a83e.webp", + "name_cn": "阿姆斯特丹/Amsterdamer Bright Virginia 30g装", + "name_jp": "アムステルダマー・ ブライトバージニア", + "description": "", + "price_jpy": 550, + "category": "Amsterdamer/阿姆斯特丹", + "price_cny": 26.4 + }, + { + "external_id": "2454", + "image_url": "https://ribenyan.com/data/images/goods/725_6913f2ad0d9b0.webp", + "name_cn": "阿姆斯特丹/Amsterdamer Dark Virginia 30g装", + "name_jp": "アムステルダマー・ ダークバージニア", + "description": "", + "price_jpy": 550, + "category": "Amsterdamer/阿姆斯特丹", + "price_cny": 26.4 + }, + { + "external_id": "2080", + "image_url": "https://ribenyan.com/data/images/goods/724_6913f204b2dec.webp", + "name_cn": "阿姆斯特丹/Amsterdamer Original Virginia 30g装", + "name_jp": "アムステルダマー・ オリジナルバージニア", + "description": "", + "price_jpy": 550, + "category": "Amsterdamer/阿姆斯特丹", + "price_cny": 26.4 + }, + { + "external_id": "2722", + "image_url": "https://ribenyan.com/data/images/goods/732_6913fcafeb6ed.webp", + "name_cn": "Bragilian Spirit American Blend 20g装", + "name_jp": "ブラジリアンスピリット・ アメリカンブレンド", + "description": "", + "price_jpy": 480, + "category": "Bragilian Spirit", + "price_cny": 23.04 + }, + { + "external_id": "2723", + "image_url": "https://ribenyan.com/data/images/goods/733_6913fd231bc57.webp", + "name_cn": "Bragilian Spirit Classic 20g装", + "name_jp": "ブラジリアンスピリット・ クラシック", + "description": "", + "price_jpy": 480, + "category": "Bragilian Spirit", + "price_cny": 23.04 + }, + { + "external_id": "2725", + "image_url": "https://ribenyan.com/data/images/goods/735_6913fe37bfa96.webp", + "name_cn": "Bragilian Spirit Virginia Blend 20g装", + "name_jp": "ブラジリアンスピリット・ バージニアブレンド", + "description": "", + "price_jpy": 480, + "category": "Bragilian Spirit", + "price_cny": 23.04 + }, + { + "external_id": "2724", + "image_url": "https://ribenyan.com/data/images/goods/734_6913fdb0d4c49.webp", + "name_cn": "Bragilian Spirit Dark Fired 20g装", + "name_jp": "ブラジリアンスピリット・ ダークファイヤード", + "description": "", + "price_jpy": 480, + "category": "Bragilian Spirit", + "price_cny": 23.04 + }, + { + "external_id": "3856", + "image_url": "https://ribenyan.com/data/images/goods/727_6913f94e0e0f1.webp", + "name_cn": "IROHA 30g装", + "name_jp": "いろは", + "description": "", + "price_jpy": 750, + "category": "IROHA", + "price_cny": 36.0 + }, + { + "external_id": "2693", + "image_url": "https://ribenyan.com/data/images/goods/736_6913ff066da55.webp", + "name_cn": "RYTUA Zambesi 25g装", + "name_jp": "リトゥア・ザンベジ", + "description": "", + "price_jpy": 650, + "category": "RYTUA", + "price_cny": 31.2 + }, + { + "external_id": "2694", + "image_url": "https://ribenyan.com/data/images/goods/737_6913ff7b523b2.webp", + "name_cn": "RYTUA Jatim 25g装", + "name_jp": "リトゥア・ジャティム", + "description": "", + "price_jpy": 650, + "category": "RYTUA", + "price_cny": 31.2 + }, + { + "external_id": "4211", + "image_url": "https://ribenyan.com/data/images/goods/729_6913fa5fe04f5.webp", + "name_cn": "宝船 20g装", + "name_jp": "宝船", + "description": "", + "price_jpy": 590, + "category": "宝船", + "price_cny": 28.32 + }, + { + "external_id": "2340", + "image_url": "https://ribenyan.com/data/images/goods/730_6913faeee092e.webp", + "name_cn": "宝船 Gold 20g装", + "name_jp": "宝船ゴールド", + "description": "", + "price_jpy": 590, + "category": "宝船", + "price_cny": 28.32 + }, + { + "external_id": "4138", + "image_url": "https://ribenyan.com/data/images/goods/728_6913f9eb68a9f.webp", + "name_cn": "小粹 10g装", + "name_jp": "こいき", + "description": "", + "price_jpy": 600, + "category": "小粹", + "price_cny": 28.8 + }, + { + "external_id": "2894", + "image_url": "https://ribenyan.com/data/images/goods/731_6913fc3700ed9.webp", + "name_cn": "松风 30g装", + "name_jp": "松風", + "description": "", + "price_jpy": 830, + "category": "松风", + "price_cny": 39.84 + } +] \ No newline at end of file diff --git a/scripts/check_admin.py b/scripts/check_admin.py new file mode 100644 index 0000000..59a667e --- /dev/null +++ b/scripts/check_admin.py @@ -0,0 +1,7 @@ +import sqlite3 +conn = sqlite3.connect('backend/sale.db') +cursor = conn.cursor() +cursor.execute("PRAGMA table_info(users)") +print("Users table columns:") +for col in cursor.fetchall(): + print(col) diff --git a/scripts/check_categories.py b/scripts/check_categories.py new file mode 100644 index 0000000..213a509 --- /dev/null +++ b/scripts/check_categories.py @@ -0,0 +1,12 @@ +import sqlite3 +conn = sqlite3.connect('backend/sale.db') +cursor = conn.cursor() +cursor.execute("SELECT id, name FROM categories") +print("Categories in database:") +for row in cursor.fetchall(): + print(f" ID: {row[0]}, Name: {row[1]}") + +cursor.execute("SELECT product_id, category_id FROM product_categories") +print("\nProduct-Category relations:") +for row in cursor.fetchall(): + print(f" Product ID: {row[0]}, Category ID: {row[1]}") diff --git a/scripts/check_products.py b/scripts/check_products.py new file mode 100644 index 0000000..a1ec850 --- /dev/null +++ b/scripts/check_products.py @@ -0,0 +1,7 @@ +import sqlite3 +conn = sqlite3.connect('backend/sale.db') +cursor = conn.cursor() +cursor.execute("SELECT id, name, price FROM products ORDER BY id DESC LIMIT 25") +print("Products in database:") +for row in cursor.fetchall(): + print(f" ID: {row[0]}, Name: {row[1]}, Price: {row[2]}") diff --git a/scripts/import_products.py b/scripts/import_products.py new file mode 100644 index 0000000..ad82155 --- /dev/null +++ b/scripts/import_products.py @@ -0,0 +1,156 @@ +import requests +import json +import os +import sys + +API_BASE = "http://localhost:8080/api" + +ADMIN_TOKEN = None + +def login(): + global ADMIN_TOKEN + response = requests.post(f"{API_BASE}/auth/login", json={ + "email": "admin@viaeon.com", + "password": "admin123" + }) + if response.status_code == 200: + data = response.json() + ADMIN_TOKEN = data.get("token") + print(f"登录成功") + return True + else: + print(f"登录失败: {response.text}") + return False + +def get_headers(): + return { + "Authorization": f"Bearer {ADMIN_TOKEN}", + "Content-Type": "application/json" + } + +def create_category(name): + response = requests.post(f"{API_BASE}/admin/categories", + headers=get_headers(), + json={"name": name} + ) + if response.status_code == 200: + data = response.json() + return data.get("id") + else: + existing = requests.get(f"{API_BASE}/categories") + if existing.status_code == 200: + cats = existing.json() + if isinstance(cats, list): + for cat in cats: + if isinstance(cat, dict) and cat.get("name") == name: + return cat.get("id") + elif isinstance(cat, str) and cat == name: + return None + return None + +def download_image(url, filepath): + try: + response = requests.get(url, timeout=30) + if response.status_code == 200: + with open(filepath, 'wb') as f: + f.write(response.content) + return True + except Exception as e: + print(f"下载图片失败: {e}") + return False + +def upload_image(filepath): + try: + with open(filepath, 'rb') as f: + response = requests.post(f"{API_BASE}/upload", + headers={"Authorization": f"Bearer {ADMIN_TOKEN}"}, + files={"file": f} + ) + if response.status_code == 200: + data = response.json() + return data.get("url") + except Exception as e: + print(f"上传图片失败: {e}") + return None + +def create_product(product, category_id, image_url): + price_cny = product.get("price_jpy", 0) * 0.048 + price_cny = round(price_cny, 2) + + data = { + "name": product.get("name_cn", product.get("name_jp", "未知商品")), + "description": f"{product.get('name_jp', '')}\n{product.get('description', '')}".strip(), + "price": price_cny, + "stock": 100, + "images": image_url, + "category_ids": [category_id] if category_id else [], + "is_active": True + } + + response = requests.post(f"{API_BASE}/admin/products", + headers=get_headers(), + json=data + ) + + if response.status_code in [200, 201]: + return response.json() + else: + result = response.json() + if result.get("data") and result["data"].get("id"): + return result["data"] + print(f"创建商品失败: {response.text}") + return None + +def main(): + print("开始导入商品数据...") + + if not login(): + print("请先创建管理员账户") + return + + with open('ribenyan_products.json', 'r', encoding='utf-8') as f: + products = json.load(f) + + print(f"读取到 {len(products)} 个商品") + + categories = {} + for product in products: + cat_name = product.get("category") + if cat_name not in categories: + cat_id = create_category(cat_name) + if cat_id: + categories[cat_name] = cat_id + print(f"创建分类: {cat_name} (ID: {cat_id})") + + os.makedirs("temp_images", exist_ok=True) + + success_count = 0 + for i, product in enumerate(products): + print(f"\n处理商品 {i+1}/{len(products)}: {product.get('name_cn', 'N/A')}") + + image_url = "" + if product.get("image_url"): + temp_path = f"temp_images/{product.get('external_id', i)}.webp" + if download_image(product["image_url"], temp_path): + uploaded_url = upload_image(temp_path) + if uploaded_url: + image_url = uploaded_url + print(f" 图片上传成功: {uploaded_url}") + + cat_name = product.get("category") + cat_id = categories.get(cat_name) + + result = create_product(product, cat_id, image_url) + if result: + success_count += 1 + print(f" 商品创建成功") + else: + print(f" 商品创建失败") + + import shutil + shutil.rmtree("temp_images", ignore_errors=True) + + print(f"\n导入完成: 成功 {success_count}/{len(products)} 个商品") + +if __name__ == "__main__": + main() diff --git a/scripts/migrate_brands.py b/scripts/migrate_brands.py new file mode 100644 index 0000000..0d7c638 --- /dev/null +++ b/scripts/migrate_brands.py @@ -0,0 +1,72 @@ +import sqlite3 + +conn = sqlite3.connect('backend/sale.db') +cursor = conn.cursor() + +cursor.execute("SELECT id, name FROM categories") +categories = cursor.fetchall() + +print(f"Found {len(categories)} categories") + +brand_keywords = [ + 'MEVIUS', 'Camel', 'Marlboro', 'Lucky', 'Kent', 'Seven Stars', 'Winston', + 'Lark', 'Pianissimo', 'Caster', 'Hope', 'Peace', 'Cabin', 'Golden Bat', + 'Black Spider', 'Che', 'Ark Royal', 'Cigaronne', 'TEREA', 'IQOS', 'HEETS', + 'Fiit', 'neo', 'sentia', 'virto', 'BON', 'LEAF', 'glow', 'Ploom', + 'American Spirit', 'Pall Mall', 'Davidoff', 'Dunhill', 'Parliament', + 'Virginia', 'Mild Seven', 'Salem', 'Kool', 'Newport', 'Carlton', + 'Merit', 'Vantage', 'Now', 'Tareyton', 'Belair', 'L&M', + 'Bond', 'Vogue', 'Rothmans', 'Gauloises', 'Gitanes', 'Prince', + 'メビウス', 'キャメル', 'マールボロ', 'ラッキー', 'ケント', 'セブンスター', 'ウィンストン', + 'ピアニッシモ', 'キャスター', 'ホープ', 'ピース', 'キャビン', 'ゴールデンバット', + 'ブラック', 'チェ', 'アーク', 'シガローネ', 'テリア', 'アイコス', 'ヒーツ', + 'フィット', 'ネオ', 'センシア', 'ヴェルト', 'ボン', 'リーフ', 'グロー', 'プルーム', + 'アメリカンスピリット', 'パーラメント', 'バージニア', 'マイルドセブン', 'セーラム', 'クール', + 'ラーク', 'ホープ', 'ピース', 'キャビン', 'キャメル', 'マールボロ' +] + +migrated_count = 0 +for cat_id, cat_name in categories: + is_brand = False + for keyword in brand_keywords: + if keyword.lower() in cat_name.lower(): + is_brand = True + break + + if is_brand: + cursor.execute("SELECT id FROM brands WHERE name = ?", (cat_name,)) + existing = cursor.fetchone() + if existing: + brand_id = existing[0] + else: + cursor.execute("INSERT INTO brands (name, created_at, updated_at) VALUES (?, datetime('now'), datetime('now'))", (cat_name,)) + brand_id = cursor.lastrowid + + cursor.execute("UPDATE products SET brand_id = ? WHERE id IN (SELECT product_id FROM product_categories WHERE category_id = ?)", (brand_id, cat_id)) + cursor.execute("DELETE FROM product_categories WHERE category_id = ?", (cat_id,)) + cursor.execute("DELETE FROM categories WHERE id = ?", (cat_id,)) + migrated_count += 1 + print(f"Migrated brand: {cat_name}") + +print(f"\nMigrated {migrated_count} brands") + +new_categories = [ + ('卷烟', '传统卷烟产品'), + ('IQOS烟弹', 'IQOS专用加热烟弹'), + ('手卷烟丝', '手卷烟丝和烟纸'), + ('雪茄', '雪茄产品'), + ('电子烟', '电子烟及相关产品'), + ('薄荷烟', '薄荷口味卷烟'), + ('超细烟', '超细支卷烟'), +] + +for name, desc in new_categories: + cursor.execute("SELECT id FROM categories WHERE name = ?", (name,)) + if not cursor.fetchone(): + cursor.execute("INSERT INTO categories (name, description, created_at, updated_at) VALUES (?, ?, datetime('now'), datetime('now'))", (name, desc)) + print(f"Created category: {name}") + +conn.commit() +conn.close() + +print("\nMigration completed!") diff --git a/scripts/reset_password.py b/scripts/reset_password.py new file mode 100644 index 0000000..8c3059d --- /dev/null +++ b/scripts/reset_password.py @@ -0,0 +1,13 @@ +import sqlite3 +import bcrypt + +conn = sqlite3.connect('backend/sale.db') +cursor = conn.cursor() + +new_password = "admin123" +hashed = bcrypt.hashpw(new_password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') + +cursor.execute("UPDATE users SET password_hash = ? WHERE email = ?", (hashed, "admin@viaeon.com")) +conn.commit() + +print("密码已重置为: admin123") diff --git a/scripts/scraper_ribenyan.py b/scripts/scraper_ribenyan.py new file mode 100644 index 0000000..8779d2c --- /dev/null +++ b/scripts/scraper_ribenyan.py @@ -0,0 +1,181 @@ +import requests +from bs4 import BeautifulSoup +import json +import re +import time + +BASE_URL = "https://ribenyan.com" +HEADERS = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,ja;q=0.7", +} + +def fetch_page(url): + try: + response = requests.get(url, headers=HEADERS, timeout=30) + response.encoding = 'utf-8' + return response.text + except Exception as e: + print(f"Error fetching {url}: {e}") + return None + +def get_category_links(html): + soup = BeautifulSoup(html, 'html.parser') + categories = [] + + accordion = soup.find('div', id='goodstypeaccordion') + if accordion: + links = accordion.find_all('a', class_='list-group-item') + for link in links: + href = link.get('href', '') + text = link.get_text(strip=True) + if href and 'm=goods&a=list' in href: + if href.startswith('./'): + href = BASE_URL + '/' + href[2:] + elif href.startswith('index.php'): + href = BASE_URL + '/' + href + elif not href.startswith('http'): + href = BASE_URL + '/' + href + categories.append({'name': text, 'url': href}) + + return categories + +def parse_products(html): + products = [] + soup = BeautifulSoup(html, 'html.parser') + + rows = soup.find_all('div', class_='d-flex') + + product_rows = [] + for row in rows: + classes = row.get('class', []) + if 'py-2' in classes and 'border-bottom' in classes: + product_rows.append(row) + + for row in product_rows: + try: + product = {} + + id_div = row.find('div', class_='position-absolute') + if id_div and 'top-0' in id_div.get('class', []) and 'end-0' in id_div.get('class', []): + product['external_id'] = id_div.get_text(strip=True) + + img = row.find('img', class_='img-thumbnail') + if img: + src = img.get('src', '') + if src: + if src.startswith('./'): + src = BASE_URL + '/' + src[2:] + elif not src.startswith('http'): + src = BASE_URL + src + product['image_url'] = src + + p_tags = row.find_all('p') + name_found = False + for p in p_tags: + classes = p.get('class', []) + if 'mb-1' in classes and 'text-muted' not in classes and 'text-body-tertiary' not in classes and 'text-info' not in classes: + if not name_found: + product['name_cn'] = p.get_text(strip=True) + name_found = True + elif 'mb-1' in classes and 'text-muted' in classes: + product['name_jp'] = p.get_text(strip=True) + elif 'text-body-tertiary' in classes or 'text-info' in classes: + text = p.get_text(strip=True) + if text and '日元' not in text and '整大包' not in text and '限购' not in text: + product['description'] = text + + if not product.get('description'): + product['description'] = '' + + price_p = row.find('p', class_='mb-3') + if price_p: + price_text = price_p.get_text(strip=True) + price_match = re.search(r'(\d+)', price_text) + if price_match: + product['price_jpy'] = int(price_match.group(1)) + + if product.get('name_cn') or product.get('name_jp'): + products.append(product) + + except Exception as e: + continue + + return products + +def categorize_product(product, category_name=None): + if category_name: + return category_name + + name = product.get('name_cn', '') + ' ' + product.get('name_jp', '') + name_lower = name.lower() + + if 'iqos' in name_lower or 'terea' in name_lower or 'iluma' in name_lower or 'sentia' in name_lower or 'virto' in name_lower or 'neo' in name_lower: + return 'IQOS烟弹' + elif 'シャグ' in name or 'shag' in name_lower or '手卷' in name or '烟丝' in name: + return '手卷烟丝' + elif 'シガー' in name or 'cigar' in name_lower or '雪茄' in name: + return '雪茄' + elif 'スーパースリム' in name or 'super slim' in name_lower or '超细' in name or '细支' in name: + return '超细烟' + elif 'メンソール' in name or 'menthol' in name_lower or '薄荷' in name or '爆珠' in name: + return '薄荷烟' + else: + return '卷烟' + +def main(): + print("开始采集 ribenyan.com 所有商品数据...") + + html = fetch_page(BASE_URL) + if not html: + print("无法获取首页数据") + return + + categories = get_category_links(html) + print(f"发现 {len(categories)} 个分类") + + all_products = [] + seen_ids = set() + + for i, cat in enumerate(categories): + print(f"\n[{i+1}/{len(categories)}] 采集分类: {cat['name']}") + + cat_html = fetch_page(cat['url']) + if not cat_html: + continue + + products = parse_products(cat_html) + + for p in products: + p['category'] = categorize_product(p, cat['name']) + p['price_cny'] = round(p.get('price_jpy', 0) * 0.048, 2) + + pid = p.get('external_id') + if pid and pid not in seen_ids: + seen_ids.add(pid) + all_products.append(p) + + print(f" 获取 {len(products)} 个商品,累计 {len(all_products)} 个") + time.sleep(0.5) + + output_file = 'ribenyan_products.json' + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(all_products, f, ensure_ascii=False, indent=2) + + print(f"\n采集完成,共获取 {len(all_products)} 个商品") + print(f"数据已保存到 {output_file}") + + cat_stats = {} + for p in all_products: + cat = p.get('category', '其他') + if cat not in cat_stats: + cat_stats[cat] = 0 + cat_stats[cat] += 1 + + print("\n分类统计:") + for cat, count in sorted(cat_stats.items(), key=lambda x: -x[1]): + print(f" {cat}: {count} 个") + +if __name__ == "__main__": + main() diff --git a/商品售卖网站技术文档.md b/商品售卖网站技术文档.md new file mode 100644 index 0000000..085ae97 --- /dev/null +++ b/商品售卖网站技术文档.md @@ -0,0 +1,752 @@ +# 商品售卖网站技术文档 + +## 1. 项目概述 + +本项目是一个多功能商品售卖平台,支持管理员后台、供货商后台和用户前端,具备完整的商品管理、订单处理、库存管理、多语言支持等功能。 + +## 2. 技术栈选择 + +### 2.1 前端技术 +- **框架**:Vue 3 + Vite +- **状态管理**:Pinia +- **路由**:Vue Router +- **UI 库**:Element Plus +- **样式**:SCSS +- **国际化**:i18n +- **HTTP 客户端**:Axios + +### 2.2 后端技术 +- **语言**:Go 1.20+ +- **Web 框架**:Gin +- **数据库**:SQLite(默认)/ MySQL / PostgreSQL +- **缓存**:Redis +- **认证**:JWT +- **ORM**:GORM +- **邮件服务**:SMTP + +## 3. 系统架构 + +### 3.1 架构图 + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ 前端层 │ +├─────────────────────┬─────────────────────┬───────────────────────┤ +│ 用户前端 │ 管理员后台 │ 供货商后台 │ +└─────────┬──────────┴─────────┬──────────┴──────────┬──────────────┘ + │ │ │ +┌─────────▼────────────────────▼────────────────────▼──────────────┐ +│ API 层 │ +└───────────────────────────────┬──────────────────────────────────┘ + │ +┌───────────────────────────────▼──────────────────────────────────┐ +│ 服务层 │ +└───────────────────────────────┬──────────────────────────────────┘ + │ +┌───────────────────────────────▼──────────────────────────────────┐ +│ 数据层 │ +└───────────────────────────────────────────────────────────────────┘ +``` + +## 4. 功能模块设计 + +### 4.1 核心模块 + +#### 4.1.1 用户管理 +- **用户注册**:支持邮箱注册,需验证邮箱,可通过邀请码注册获得额外购买资格 +- **用户登录**:支持邮箱密码登录,可记住密码,登录失败次数限制 +- **忘记密码**:通过邮箱验证码重置密码 +- **邮箱验证**:注册和修改邮箱时需验证,确保邮箱有效性 +- **个人中心**:展示用户基本信息、购买资格、订单历史、收藏夹等 +- **收货地址管理**:支持添加、编辑、删除收货地址,设置默认地址 +- **密码修改**:定期提醒用户修改密码,支持密码强度检测 +- **邀请系统**:生成个人邀请码,邀请新用户注册可获得购买资格奖励 + +#### 4.1.2 商品管理 +- **商品分类管理**:支持多级分类,可设置分类图标、排序,支持分类购买限制 +- **品牌管理**:支持品牌创建、编辑、删除,可设置品牌Logo、描述、排序 +- **商品信息管理**:支持商品名称、描述、价格、库存、图片等基本信息管理 +- **自定义字段**:支持为商品添加自定义字段,如颜色、尺寸、材质等属性 +- **商品价格设置**:支持设置基础价格、促销价格、会员价格等 +- **购买资格设置**:设置商品是否需要购买资格才能购买 +- **购买限制设置**: + - 商品级限制:最低/最高购买数量、重量、金额 + - 分类级限制:最低/最高购买数量、重量、金额 +- **商品多分类归属**:支持一个商品属于多个分类 +- **商品品牌关联**:支持为商品关联品牌 +- **库存管理**:实时库存监控,库存预警,支持供货商独立管理库存 + +#### 4.1.3 订单管理 +- **购物车功能**:支持添加/删除商品,调整数量,查看总价,保存购物车状态 +- **订单创建与支付**:支持多种支付方式,订单创建后自动扣除购买资格 +- **订单状态跟踪**:订单状态实时更新,包括待支付、待确认、待发货、已发货、已完成、已退款等 +- **订单导出**:支持导出订单为Excel、CSV等格式 +- **订单确认与发货**:供货商可确认订单,上传发货照片、快递单照片和报关清单 +- **物流信息管理**:支持添加快递单号,实时跟踪物流状态 +- **订单退款**: + - 未发货订单可申请退款 + - 已发货订单不允许退款 + - 退款时扣除管理员设置的通道费率 + - 退款状态跟踪与管理 + +#### 4.1.4 供货商管理 +- **供货商注册与授权**:管理员添加和授权供货商,设置供货商权限 +- **商品授权管理**:管理员授权特定分类或商品给供货商管理 +- **库存管理**:供货商可独立管理授权商品的库存数量,实时更新 +- **订单处理**:供货商可查看分配给自己的订单,确认订单状态 +- **发货管理**:供货商可上传发货照片、快递单照片和报关清单,添加快递单号 +- **销售统计**:供货商可查看自己的销售数据和库存状态 + +#### 4.1.5 管理员功能 +- **仪表盘统计**:显示用户数、商品数、订单数、营收等统计数据 +- **系统设置**: + - 网站基本信息设置 + - 验证码开关设置 + - 通道费率设置 +- **SMTP 配置**:配置邮件发送服务器,用于验证码、订单通知等 +- **支付通道配置**:配置多种支付方式,如支付宝、微信支付等 +- **运费设置**:设置首重、续重费用,支持不同地区运费设置 +- **服务费设置**:设置服务费费率,支持按订单金额比例计算 +- **邀请奖励设置**:设置邀请X人赠送X个购买资格 +- **抽奖系统**:创建和管理抽奖活动,设置奖品和规则 +- **工单管理**:查看、分配、处理用户提交的工单 + +#### 4.1.6 资讯模块 +- **资讯发布**:管理员可创建、编辑、删除资讯文章 +- **资讯置顶**:支持设置资讯为置顶状态,置顶资讯优先显示 +- **资讯排序**:支持自定义排序,置顶资讯按排序字段显示 +- **资讯封面**:支持设置资讯封面图片 +- **资讯摘要**:支持设置资讯摘要信息 +- **发布控制**:支持设置资讯为草稿或已发布状态 +- **用户端浏览**:用户可浏览已发布的资讯列表和详情 + +#### 4.1.7 多语言支持 +- **语言切换**:支持中文、日语、英语三种语言切换 +- **语言配置**:管理员可配置各语言的翻译内容 +- **语言偏好**:保存用户语言偏好,下次访问自动使用 + +### 4.2 特色功能 + +#### 4.2.1 购买资格系统 +- **商品购买资格设置**:设置商品是否需要购买资格,需要多少资格 +- **邀请赠送资格**:邀请新用户注册可获得购买资格奖励 +- **抽奖获得资格**:通过参与抽奖活动获得购买资格 +- **资格管理**:用户可查看自己的购买资格数量和使用记录 + +#### 4.2.2 抽奖系统 +- **自定义抽奖物品**:支持设置实物奖品、虚拟奖品和购买资格 +- **购买权重抽奖**:根据用户购买金额计算权重,权重越高中奖概率越大 +- **随机抽奖**:完全随机抽取中奖用户 +- **周期抽奖**:支持设置每日、每周、每月等周期抽奖 +- **报名有效期设置**:设置用户报名一次,可参与多期抽奖的有效期 +- **抽奖结果公示**:自动公示抽奖结果,保证公平透明 + +#### 4.2.3 工单系统 +- **用户提交工单**:用户可提交问题、建议、投诉等工单 +- **工单分类**:支持多种工单类型,如订单问题、账户问题、商品问题等 +- **管理员处理工单**:管理员可分配、处理、回复工单 +- **工单状态跟踪**:实时更新工单状态,如待处理、处理中、已解决、已关闭等 +- **工单历史**:用户可查看自己的工单历史和处理记录 + +## 5. 数据库设计 + +### 5.0 数据库配置 + +系统支持三种数据库,通过环境变量 `DB_DRIVER` 配置: + +| DB_DRIVER 值 | 数据库 | 说明 | +| :--- | :--- | :--- | +| `sqlite` | SQLite | 默认,无需额外安装,文件存储在 `DB_FILEPATH` 指定路径 | +| `mysql` | MySQL | 需配置 `DB_HOST`、`DB_PORT`、`DB_USER`、`DB_PASSWORD`、`DB_NAME` | +| `postgres` | PostgreSQL | 需配置 `DB_HOST`、`DB_PORT`、`DB_USER`、`DB_PASSWORD`、`DB_NAME`、`DB_SSLMODE` | + +**环境变量配置示例**: +``` +# SQLite(默认) +DB_DRIVER=sqlite +DB_FILEPATH=sale.db + +# MySQL +DB_DRIVER=mysql +DB_HOST=localhost +DB_PORT=3306 +DB_USER=root +DB_PASSWORD=password +DB_NAME=sale + +# PostgreSQL +DB_DRIVER=postgres +DB_HOST=localhost +DB_PORT=5432 +DB_USER=postgres +DB_PASSWORD=password +DB_NAME=sale +DB_SSLMODE=disable +``` + +### 5.1 用户表 (users) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 用户ID | +| `username` | `VARCHAR(50)` | `UNIQUE NOT NULL` | 用户名 | +| `email` | `VARCHAR(100)` | `UNIQUE NOT NULL` | 邮箱 | +| `password_hash` | `VARCHAR(255)` | `NOT NULL` | 密码哈希 | +| `role` | `VARCHAR(20)` | `NOT NULL` | 角色(user/supplier/admin) | +| `purchase_credits` | `INTEGER` | `DEFAULT 0` | 购买资格数量 | +| `invite_code` | `VARCHAR(20)` | `UNIQUE` | 邀请码 | +| `referred_by` | `INTEGER` | `REFERENCES users(id)` | 推荐人ID | +| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 创建时间 | +| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 更新时间 | + +### 5.2 商品分类表 (categories) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 分类ID | +| `name` | `VARCHAR(100)` | `NOT NULL` | 分类名称 | +| `description` | `TEXT` | | 分类描述 | +| `parent_id` | `INTEGER` | `REFERENCES categories(id)` | 父分类ID | +| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 创建时间 | +| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 更新时间 | + +### 5.3 品牌表 (brands) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 品牌ID | +| `name` | `VARCHAR(255)` | `UNIQUE NOT NULL` | 品牌名称 | +| `description` | `TEXT` | | 品牌描述 | +| `logo` | `VARCHAR(500)` | | 品牌Logo URL | +| `sort_order` | `INTEGER` | `DEFAULT 0` | 排序 | +| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 创建时间 | +| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 更新时间 | + +### 5.4 商品表 (products) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 商品ID | +| `name` | `VARCHAR(255)` | `NOT NULL` | 商品名称 | +| `description` | `TEXT` | | 商品描述 | +| `price` | `DECIMAL(10,2)` | `NOT NULL` | 商品价格 | +| `min_purchase` | `INTEGER` | `DEFAULT 1` | 最低购买数量 | +| `max_purchase` | `INTEGER` | | 最大购买数量 | +| `min_weight` | `DECIMAL(10,2)` | | 最低购买重量 | +| `max_weight` | `DECIMAL(10,2)` | | 最大购买重量 | +| `require_credit` | `BOOLEAN` | `DEFAULT FALSE` | 是否需要购买资格 | +| `credit_cost` | `INTEGER` | `DEFAULT 0` | 购买所需资格数量 | +| `credit_reward` | `INTEGER` | `DEFAULT 0` | 购买后赠送的资格数量 | +| `images` | `TEXT` | | 商品图片(JSON数组) | +| `is_active` | `BOOLEAN` | `DEFAULT TRUE` | 是否上架 | +| `brand_id` | `INTEGER` | `REFERENCES brands(id)` | 品牌ID | +| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 创建时间 | +| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 更新时间 | + +### 5.5 商品自定义字段表 (product_custom_fields) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 字段ID | +| `product_id` | `INTEGER` | `REFERENCES products(id)` | 商品ID | +| `field_name` | `VARCHAR(100)` | `NOT NULL` | 字段名称 | +| `field_value` | `TEXT` | | 字段值 | +| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 创建时间 | + +### 5.6 库存表 (inventory) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 库存ID | +| `product_id` | `INTEGER` | `REFERENCES products(id)` | 商品ID | +| `supplier_id` | `INTEGER` | `REFERENCES users(id)` | 供货商ID | +| `quantity` | `INTEGER` | `DEFAULT 0` | 库存数量 | +| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 更新时间 | + +### 5.7 购物车表 (carts) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 购物车ID | +| `user_id` | `INTEGER` | `REFERENCES users(id)` | 用户ID | +| `product_id` | `INTEGER` | `REFERENCES products(id)` | 商品ID | +| `quantity` | `INTEGER` | `NOT NULL` | 数量 | +| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 创建时间 | +| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 更新时间 | + +### 5.8 订单表 (orders) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 订单ID | +| `user_id` | `INTEGER` | `REFERENCES users(id)` | 用户ID | +| `supplier_id` | `INTEGER` | `REFERENCES users(id)` | 供货商ID | +| `subtotal` | `DECIMAL(10,2)` | `NOT NULL` | 商品小计 | +| `shipping_fee` | `DECIMAL(10,2)` | `DEFAULT 0` | 运费 | +| `service_fee` | `DECIMAL(10,2)` | `DEFAULT 0` | 服务费 | +| `tax` | `DECIMAL(10,2)` | `DEFAULT 0` | 税费 | +| `total_amount` | `DECIMAL(10,2)` | `NOT NULL` | 订单总金额 | +| `refund_amount` | `DECIMAL(10,2)` | | 退款金额 | +| `refund_status` | `VARCHAR(20)` | | 退款状态 | +| `refund_reason` | `TEXT` | | 退款原因 | +| `status` | `VARCHAR(20)` | `NOT NULL` | 订单状态 | +| `shipping_address_id` | `INTEGER` | `REFERENCES addresses(id)` | 收货地址ID | +| `tracking_number` | `VARCHAR(100)` | | 快递单号 | +| `shipping_photo` | `TEXT` | | 发货照片 | +| `express_photo` | `TEXT` | | 快递单照片 | +| `customs_photo` | `TEXT` | | 报关清单照片 | +| `payment_method` | `VARCHAR(50)` | | 支付方式 | +| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 创建时间 | +| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 更新时间 | + +### 5.9 订单商品表 (order_items) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 订单项ID | +| `order_id` | `INTEGER` | `REFERENCES orders(id)` | 订单ID | +| `product_id` | `INTEGER` | `REFERENCES products(id)` | 商品ID | +| `quantity` | `INTEGER` | `NOT NULL` | 数量 | +| `price` | `DECIMAL(10,2)` | `NOT NULL` | 单价 | + +### 5.10 收货地址表 (addresses) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 地址ID | +| `user_id` | `INTEGER` | `REFERENCES users(id)` | 用户ID | +| `name` | `VARCHAR(50)` | `NOT NULL` | 收件人姓名 | +| `phone` | `VARCHAR(20)` | `NOT NULL` | 联系电话 | +| `address` | `TEXT` | `NOT NULL` | 详细地址 | +| `is_default` | `BOOLEAN` | `DEFAULT FALSE` | 是否默认地址 | +| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 创建时间 | +| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 更新时间 | + +### 5.11 抽奖表 (lotteries) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 抽奖ID | +| `name` | `VARCHAR(100)` | `NOT NULL` | 抽奖名称 | +| `description` | `TEXT` | | 抽奖描述 | +| `start_time` | `TIMESTAMP` | `NOT NULL` | 开始时间 | +| `end_time` | `TIMESTAMP` | `NOT NULL` | 结束时间 | +| `cycle` | `VARCHAR(20)` | | 周期类型 | +| `daily_quota` | `INTEGER` | | 每日限额 | +| `total_quota` | `INTEGER` | | 总限额 | +| `registration_validity` | `INTEGER` | | 报名有效期(天) | +| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 创建时间 | +| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 更新时间 | + +### 5.12 抽奖奖品表 (lottery_prizes) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 奖品ID | +| `lottery_id` | `INTEGER` | `REFERENCES lotteries(id)` | 抽奖ID | +| `name` | `VARCHAR(100)` | `NOT NULL` | 奖品名称 | +| `type` | `VARCHAR(20)` | `NOT NULL` | 奖品类型 | +| `quantity` | `INTEGER` | `NOT NULL` | 奖品数量 | +| `weight` | `INTEGER` | `DEFAULT 1` | 权重 | +| `credit_reward` | `INTEGER` | | 赠送资格数量 | + +### 5.13 抽奖参与表 (lottery_participants) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 参与ID | +| `lottery_id` | `INTEGER` | `REFERENCES lotteries(id)` | 抽奖ID | +| `user_id` | `INTEGER` | `REFERENCES users(id)` | 用户ID | +| `purchase_weight` | `INTEGER` | `DEFAULT 0` | 购买权重 | +| `registered_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 报名时间 | + +### 5.14 工单表 (tickets) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 工单ID | +| `user_id` | `INTEGER` | `REFERENCES users(id)` | 用户ID | +| `title` | `VARCHAR(200)` | `NOT NULL` | 工单标题 | +| `content` | `TEXT` | `NOT NULL` | 工单内容 | +| `status` | `VARCHAR(20)` | `NOT NULL` | 工单状态 | +| `assigned_to` | `INTEGER` | `REFERENCES users(id)` | 处理人ID | +| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 创建时间 | +| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 更新时间 | + +### 5.15 系统设置表 (system_settings) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 设置ID | +| `key` | `VARCHAR(100)` | `UNIQUE NOT NULL` | 设置键 | +| `value` | `TEXT` | | 设置值 | +| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 更新时间 | + +### 5.16 供应商授权表 (supplier_authorizations) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 授权ID | +| `supplier_id` | `INTEGER` | `NOT NULL` | 供货商ID | +| `type` | `VARCHAR(20)` | `NOT NULL` | 授权类型(category/product) | +| `ref_id` | `INTEGER` | `NOT NULL` | 关联ID | + +### 5.17 资讯表 (articles) + +| 字段名 | 数据类型 | 约束 | 描述 | +| :--- | :--- | :--- | :--- | +| `id` | `SERIAL` | `PRIMARY KEY` | 资讯ID | +| `title` | `VARCHAR(255)` | `NOT NULL` | 标题 | +| `content` | `TEXT` | `NOT NULL` | 内容 | +| `summary` | `VARCHAR(500)` | | 摘要 | +| `cover_image` | `VARCHAR(500)` | | 封面图URL | +| `is_pinned` | `BOOLEAN` | `DEFAULT FALSE` | 是否置顶 | +| `is_published` | `BOOLEAN` | `DEFAULT TRUE` | 是否发布 | +| `sort_order` | `INTEGER` | `DEFAULT 0` | 排序 | +| `author_id` | `INTEGER` | `REFERENCES users(id)` | 作者ID | +| `created_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 创建时间 | +| `updated_at` | `TIMESTAMP` | `DEFAULT CURRENT_TIMESTAMP` | 更新时间 | + +## 6. API 接口设计 + +### 6.1 用户相关接口 + +| API 路径 | 方法 | 模块 | 功能描述 | +| :--- | :--- | :--- | :--- | +| `/api/auth/register` | `POST` | 认证 | 用户注册 | +| `/api/auth/login` | `POST` | 认证 | 用户登录 | +| `/api/auth/forgot-password` | `POST` | 认证 | 忘记密码 | +| `/api/auth/reset-password` | `POST` | 认证 | 重置密码 | +| `/api/auth/verify-email` | `GET` | 认证 | 邮箱验证 | +| `/api/users/profile` | `GET` | 用户 | 获取个人资料 | +| `/api/users/profile` | `PUT` | 用户 | 更新个人资料 | +| `/api/users/password` | `PUT` | 用户 | 修改密码 | +| `/api/users/addresses` | `GET` | 用户 | 获取收货地址列表 | +| `/api/users/addresses` | `POST` | 用户 | 添加收货地址 | +| `/api/users/addresses/:id` | `PUT` | 用户 | 更新收货地址 | +| `/api/users/addresses/:id` | `DELETE` | 用户 | 删除收货地址 | +| `/api/users/addresses/:id/default` | `PUT` | 用户 | 设置默认地址 | + +### 6.2 商品相关接口 + +| API 路径 | 方法 | 模块 | 功能描述 | +| :--- | :--- | :--- | :--- | +| `/api/products` | `GET` | 商品 | 获取商品列表 | +| `/api/products/:id` | `GET` | 商品 | 获取商品详情 | +| `/api/admin/products` | `GET` | 商品 | 获取商品列表(管理员) | +| `/api/admin/products` | `POST` | 商品 | 创建商品 | +| `/api/admin/products/:id` | `PUT` | 商品 | 更新商品 | +| `/api/admin/products/:id` | `DELETE` | 商品 | 删除商品 | +| `/api/admin/products/:id/custom-fields` | `POST` | 商品 | 添加自定义字段 | +| `/api/categories` | `GET` | 分类 | 获取分类列表 | +| `/api/admin/categories` | `POST` | 分类 | 创建分类 | +| `/api/admin/categories/:id` | `PUT` | 分类 | 更新分类 | +| `/api/admin/categories/:id` | `DELETE` | 分类 | 删除分类 | +| `/api/brands` | `GET` | 品牌 | 获取品牌列表 | +| `/api/admin/brands` | `GET` | 品牌 | 获取品牌列表(管理员) | +| `/api/admin/brands` | `POST` | 品牌 | 创建品牌 | +| `/api/admin/brands/:id` | `PUT` | 品牌 | 更新品牌 | +| `/api/admin/brands/:id` | `DELETE` | 品牌 | 删除品牌 | + +### 6.3 购物车与订单接口 + +| API 路径 | 方法 | 模块 | 功能描述 | +| :--- | :--- | :--- | :--- | +| `/api/cart` | `GET` | 购物车 | 获取购物车内容 | +| `/api/cart` | `POST` | 购物车 | 添加商品到购物车 | +| `/api/cart/:id` | `PUT` | 购物车 | 更新购物车商品数量 | +| `/api/cart/:id` | `DELETE` | 购物车 | 删除购物车商品 | +| `/api/orders` | `GET` | 订单 | 获取用户订单列表 | +| `/api/orders/:id` | `GET` | 订单 | 获取订单详情 | +| `/api/orders` | `POST` | 订单 | 创建订单 | +| `/api/supplier/orders` | `GET` | 订单 | 获取供货商订单列表 | +| `/api/supplier/orders/:id/confirm` | `PUT` | 订单 | 确认订单 | +| `/api/supplier/orders/:id/ship` | `PUT` | 订单 | 确认发货 | +| `/api/admin/orders` | `GET` | 订单 | 获取所有订单 | +| `/api/admin/orders/export` | `GET` | 订单 | 导出订单表格 | + +### 6.4 库存管理接口 + +| API 路径 | 方法 | 模块 | 功能描述 | +| :--- | :--- | :--- | :--- | +| `/api/supplier/inventory` | `GET` | 库存 | 获取供货商库存列表 | +| `/api/supplier/inventory/:id` | `PUT` | 库存 | 更新库存数量 | +| `/api/admin/inventory` | `GET` | 库存 | 获取所有库存 | + +### 6.5 供货商管理接口 + +| API 路径 | 方法 | 模块 | 功能描述 | +| :--- | :--- | :--- | :--- | +| `/api/admin/suppliers` | `GET` | 供货商 | 获取供货商列表 | +| `/api/admin/suppliers` | `POST` | 供货商 | 添加供货商 | +| `/api/admin/suppliers/:id` | `PUT` | 供货商 | 更新供货商信息 | +| `/api/admin/suppliers/:id` | `DELETE` | 供货商 | 删除供货商 | +| `/api/admin/suppliers/:id/authorize` | `POST` | 供货商 | 授权商品/分类 | + +### 6.6 抽奖系统接口 + +| API 路径 | 方法 | 模块 | 功能描述 | +| :--- | :--- | :--- | :--- | +| `/api/lotteries` | `GET` | 抽奖 | 获取抽奖列表 | +| `/api/lotteries/:id` | `GET` | 抽奖 | 获取抽奖详情 | +| `/api/lotteries/:id/register` | `POST` | 抽奖 | 报名抽奖 | +| `/api/admin/lotteries` | `POST` | 抽奖 | 创建抽奖 | +| `/api/admin/lotteries/:id` | `PUT` | 抽奖 | 更新抽奖 | +| `/api/admin/lotteries/:id` | `DELETE` | 抽奖 | 删除抽奖 | +| `/api/admin/lotteries/:id/prizes` | `POST` | 抽奖 | 添加奖品 | +| `/api/admin/lotteries/:id/draw` | `POST` | 抽奖 | 执行抽奖 | + +### 6.7 工单系统接口 + +| API 路径 | 方法 | 模块 | 功能描述 | +| :--- | :--- | :--- | :--- | +| `/api/tickets` | `GET` | 工单 | 获取用户工单列表 | +| `/api/tickets` | `POST` | 工单 | 提交工单 | +| `/api/tickets/:id` | `GET` | 工单 | 获取工单详情 | +| `/api/admin/tickets` | `GET` | 工单 | 获取所有工单 | +| `/api/admin/tickets/:id` | `PUT` | 工单 | 更新工单状态 | +| `/api/admin/tickets/:id/assign` | `PUT` | 工单 | 分配工单 | + +### 6.8 系统设置接口 + +| API 路径 | 方法 | 模块 | 功能描述 | +| :--- | :--- | :--- | :--- | +| `/api/settings/public` | `GET` | 设置 | 获取公共设置(费用费率等) | +| `/api/admin/stats` | `GET` | 统计 | 获取仪表盘统计数据 | +| `/api/admin/settings` | `GET` | 设置 | 获取系统设置 | +| `/api/admin/settings` | `PUT` | 设置 | 更新系统设置 | +| `/api/admin/settings/smtp` | `PUT` | 设置 | 更新SMTP设置 | +| `/api/admin/settings/payment` | `PUT` | 设置 | 更新支付通道设置 | + +### 6.9 资讯模块接口 + +| API 路径 | 方法 | 模块 | 功能描述 | +| :--- | :--- | :--- | :--- | +| `/api/articles` | `GET` | 资讯 | 获取已发布资讯列表 | +| `/api/articles/:id` | `GET` | 资讯 | 获取资讯详情 | +| `/api/admin/articles` | `GET` | 资讯 | 获取所有资讯(含草稿) | +| `/api/admin/articles` | `POST` | 资讯 | 创建资讯 | +| `/api/admin/articles/:id` | `PUT` | 资讯 | 更新资讯 | +| `/api/admin/articles/:id` | `DELETE` | 资讯 | 删除资讯 | +| `/api/admin/articles/:id/pin` | `PUT` | 资讯 | 切换置顶状态 | + +## 7. 前端设计 + +### 7.1 设计风格 + +#### 方案一:现代化科技感(推荐) +- **风格特点**:采用简洁、现代的设计风格,类似于科技企业网站 +- **配色方案**:以蓝色为主色调,搭配白色和浅灰色,营造专业、科技的氛围 +- **字体**:使用无衬线字体,如 Roboto、Inter 等 +- **布局**:响应式设计,适配不同设备屏幕 +- **动画效果**:适度的过渡动画,提升用户体验 +- **特色**:简洁明了的界面,突出商品信息,操作流程顺畅 + +#### 方案二:简约商务风 +- **风格特点**:简约、专业的商务风格,注重信息的清晰展示 +- **配色方案**:以灰色为主色调,搭配深蓝和白色,营造稳重、专业的氛围 +- **字体**:使用无衬线字体,如 Helvetica、Arial 等 +- **布局**:网格布局,信息层次分明 +- **动画效果**:轻微的过渡效果,保持界面稳定感 +- **特色**:信息密度高,适合商务用户,操作流程清晰 + +#### 方案三:时尚活力风 +- **风格特点**:时尚、活力的设计风格,吸引年轻用户群体 +- **配色方案**:以橙色和白色为主色调,搭配浅蓝和浅灰,营造活力、时尚的氛围 +- **字体**:使用现代无衬线字体,如 Poppins、Montserrat 等 +- **布局**:卡片式布局,视觉层次丰富 +- **动画效果**:活泼的微动画,增强用户互动 +- **特色**:视觉冲击力强,适合时尚商品,用户体验愉悦 + +#### 方案四:豆包飞书风(暗色毛玻璃) +- **风格特点**:暗色主题、毛玻璃效果、大字排版,类似豆包/飞书演示风格 +- **配色方案**:以深蓝紫渐变为主色调(#1a1a2e → #16213e → #0f3460),搭配半透明白色毛玻璃卡片,强调色为亮紫/亮蓝 +- **字体**:使用现代无衬线字体,如 Inter、SF Pro Display 等,标题大号加粗 +- **布局**:全屏渐变背景 + 毛玻璃卡片悬浮布局,Hero区域大字标题居中 +- **动画效果**:渐变背景微动、卡片悬浮阴影变化、淡入淡出过渡 +- **特色**: + - 深色渐变背景营造沉浸感 + - 毛玻璃(backdrop-filter: blur)卡片效果 + - 大字标题 + 简洁描述的Hero布局 + - 圆角卡片 + 微光边框 + - 适合高端产品展示,科技感与品质感并存 + +### 7.2 页面结构 + +#### 7.2.1 用户前端 +- **首页**:商品展示、分类导航、抽奖活动 +- **商品列表页**:筛选、排序、分页 +- **商品详情页**:商品信息、自定义字段、购买选项 +- **购物车页**:商品列表、数量调整、结算 +- **订单确认页**:收货地址选择、支付方式选择 +- **个人中心**:订单管理、收货地址、账户设置、购买资格 +- **抽奖页面**:抽奖活动列表、报名入口 +- **工单页面**:工单提交、工单列表 +- **资讯页面**:资讯列表、资讯详情 + +#### 7.2.2 管理员后台 +- **仪表盘**:系统概览、统计数据(用户数、商品数、订单数、营收) +- **用户管理**:用户列表、权限管理 +- **商品管理**:商品列表、分类管理、品牌管理、库存管理 +- **订单管理**:订单列表、订单详情、导出功能、退款管理 +- **供货商管理**:供货商列表、授权管理 +- **抽奖管理**:抽奖活动创建、奖品管理、抽奖执行 +- **工单管理**:工单列表、工单处理 +- **系统设置**:SMTP设置、支付通道设置、运费设置、服务费设置、税费设置、通道费率设置 +- **资讯管理**:资讯发布、编辑、删除、置顶 + +#### 7.2.3 供货商后台 +- **仪表盘**:订单统计、库存概览 +- **订单管理**:订单列表、订单确认、发货管理 +- **库存管理**:库存列表、库存更新 +- **商品管理**:授权商品列表 + +### 7.3 多语言支持 +- 使用 Vue i18n 实现多语言切换 +- 支持中文、日语、英语三种语言 +- 语言文件采用 JSON 格式存储 +- 实现语言切换功能,保存用户语言偏好 + +## 8. 安全考虑 + +### 8.1 前端安全 +- 使用 HTTPS 协议 +- 实现 CSRF 保护 +- 输入验证和 sanitization +- 密码强度检查 +- 防止 XSS 攻击 + +### 8.2 后端安全 +- 密码哈希存储 +- JWT 认证机制 +- 权限控制 +- 防止 SQL 注入 +- 速率限制 +- 日志记录 + +### 8.3 数据安全 +- 数据库加密 +- 敏感数据保护 +- 定期数据备份 + +## 9. 部署方案 + +### 9.1 开发环境 +- **前端**:Vite 开发服务器 +- **后端**:Go 本地开发服务器 +- **数据库**:PostgreSQL 本地实例 +- **缓存**:Redis 本地实例 + +### 9.2 生产环境 +- **前端**:Nginx 静态文件服务 +- **后端**:Go 应用服务器 +- **数据库**:PostgreSQL 数据库集群 +- **缓存**:Redis 集群 +- **负载均衡**:Nginx 或云服务负载均衡 +- **容器化**:Docker + Kubernetes + +## 10. 项目结构 + +### 10.1 前端项目结构 + +``` +frontend/ +├── public/ # 静态资源 +├── src/ +│ ├── assets/ # 图片、样式等 +│ ├── components/ # 通用组件 +│ ├── views/ # 页面组件 +│ │ ├── user/ # 用户前端页面 +│ │ ├── admin/ # 管理员后台页面 +│ │ └── supplier/ # 供货商后台页面 +│ ├── router/ # 路由配置 +│ ├── store/ # Pinia 状态管理 +│ ├── i18n/ # 国际化文件 +│ ├── api/ # API 调用 +│ ├── utils/ # 工具函数 +│ ├── App.vue # 根组件 +│ └── main.ts # 入口文件 +├── .env # 环境变量 +├── vite.config.ts # Vite 配置 +└── package.json # 依赖管理 +``` + +### 10.2 后端项目结构 + +``` +backend/ +├── cmd/ +│ └── server/ # 应用入口 +├── internal/ +│ ├── api/ # API 处理 +│ │ ├── handlers/ # 请求处理器 +│ │ ├── middlewares/ # 中间件 +│ │ └── routes/ # 路由配置 +│ ├── services/ # 业务逻辑 +│ ├── models/ # 数据模型 +│ ├── schemas/ # 数据校验 +│ ├── utils/ # 工具函数 +│ └── config/ # 配置管理 +├── migrations/ # 数据库迁移 +├── go.mod # Go 模块文件 +└── .env # 环境变量 +``` + +## 11. 开发计划 + +### 11.1 阶段一:基础架构搭建 +- 前端项目初始化 +- 后端项目初始化 +- 数据库设计与迁移 +- 基础 API 实现 + +### 11.2 阶段二:核心功能实现 +- 用户认证系统 +- 商品管理系统 +- 购物车与订单系统 +- 库存管理系统 + +### 11.3 阶段三:特色功能实现 +- 购买资格系统 +- 抽奖系统 +- 工单系统 +- 多语言支持 + +### 11.4 阶段四:后台管理 +- 管理员后台 +- 供货商后台 +- 系统设置 + +### 11.5 阶段五:测试与部署 +- 功能测试 +- 性能测试 +- 安全测试 +- 生产环境部署 + +## 12. 技术挑战与解决方案 + +### 12.1 多语言支持 +- **挑战**:确保所有界面元素和数据都能正确翻译 +- **解决方案**:使用 Vue i18n 管理翻译文件,实现语言切换功能 + +### 12.2 购买资格系统 +- **挑战**:实现复杂的购买资格逻辑 +- **解决方案**:设计专门的资格管理模块,记录和验证用户购买资格 + +### 12.3 抽奖系统 +- **挑战**:实现公平、透明的抽奖机制 +- **解决方案**:使用随机数生成器,结合购买权重计算中奖概率 + +### 12.4 订单处理流程 +- **挑战**:协调用户、供货商和管理员的订单处理流程 +- **解决方案**:设计清晰的订单状态流转,实现自动化通知 + +### 12.5 性能优化 +- **挑战**:确保系统在高并发情况下的性能 +- **解决方案**:使用 Redis 缓存热点数据,优化数据库查询 + +## 13. 总结 + +本技术文档详细描述了商品售卖网站的设计与实现方案,包括系统架构、功能模块、数据库设计、API 接口、前端设计等方面。通过采用现代化的技术栈和合理的架构设计,系统能够满足用户的各种需求,提供良好的用户体验。 + +项目将按照开发计划分阶段实施,确保系统的质量和稳定性。同时,系统设计考虑了未来的扩展性,为后续功能的添加和优化预留了空间。 \ No newline at end of file