增加基础设置页面
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
@@ -18,6 +19,7 @@ func Init(cfg *config.DatabaseConfig) error {
|
||||
DB, err = gorm.Open(sqlite.Open(cfg.DSN), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Info),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ require (
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/yuin/goldmark v1.8.5
|
||||
golang.org/x/crypto v0.48.0
|
||||
gorm.io/gorm v1.31.1
|
||||
golang.org/x/crypto v0.53.0
|
||||
gorm.io/driver/sqlite v1.6.0
|
||||
gorm.io/gorm v1.31.2
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -29,15 +30,17 @@ require (
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/quic-go/qpack v0.5.1 // indirect
|
||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||
@@ -53,15 +56,15 @@ require (
|
||||
go.uber.org/mock v0.5.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/arch v0.20.0 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
golang.org/x/mod v0.37.0 // indirect
|
||||
golang.org/x/net v0.56.0 // indirect
|
||||
golang.org/x/sync v0.21.0 // indirect
|
||||
golang.org/x/sys v0.46.0 // indirect
|
||||
golang.org/x/text v0.39.0 // indirect
|
||||
golang.org/x/tools v0.47.0 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // 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
|
||||
modernc.org/libc v1.74.1 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.54.0 // indirect
|
||||
)
|
||||
|
||||
@@ -46,10 +46,12 @@ github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
|
||||
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/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
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=
|
||||
@@ -66,10 +68,14 @@ 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/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
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=
|
||||
@@ -78,7 +84,6 @@ github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI=
|
||||
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg=
|
||||
github.com/quic-go/quic-go v0.54.0 h1:6s1YB9QotYI6Ospeiguknbp2Znb/jZYjZLRXn9kMQBg=
|
||||
github.com/quic-go/quic-go v0.54.0/go.mod h1:e68ZEaCdyviluZmy44P6Iey98v/Wfz6HCjQEm+l8zTY=
|
||||
github.com/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/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
@@ -118,21 +123,21 @@ go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto=
|
||||
golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio=
|
||||
golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ=
|
||||
golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0=
|
||||
golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
|
||||
golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus=
|
||||
golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM=
|
||||
golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
|
||||
golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -141,13 +146,35 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8
|
||||
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/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=
|
||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||
gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8=
|
||||
gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo=
|
||||
gorm.io/gorm v1.31.2/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
modernc.org/cc/v4 v4.29.0 h1:CXgwL8cvxmyzBQZzbSl/6xFtMCryb6u8IOqDci39cgc=
|
||||
modernc.org/cc/v4 v4.29.0/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||
modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU=
|
||||
modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk=
|
||||
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI=
|
||||
modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||
modernc.org/libc v1.74.1 h1:bdR4VTKFMC4966QSNZ05XLGI/VwzVa2kTUX51Dm0riQ=
|
||||
modernc.org/libc v1.74.1/go.mod h1:uH4t5bOx3G3g9Xcmj10YKlTcVISlRDwv8VoQJG9n8Os=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.54.0 h1:JCxR4qwkJvOaqAoYcgDoO25Nc+ROg6EJ2LfBVzdrgog=
|
||||
modernc.org/sqlite v1.54.0/go.mod h1:4ntCLuNmnH8+GNqjka1wNg7KJd5/Hi5FYp8K+XQ7GZw=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
|
||||
+17
-2
@@ -7,6 +7,7 @@ import (
|
||||
"goblog/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 创建评论请求
|
||||
@@ -24,7 +25,10 @@ func GetComments(c *gin.Context) {
|
||||
postID := c.Query("post_id")
|
||||
status := c.Query("status")
|
||||
|
||||
db := database.DB.Model(&models.Comment{}).Preload("User")
|
||||
db := database.DB.Model(&models.Comment{}).Preload("User").
|
||||
Preload("Post", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Select("id", "title", "slug")
|
||||
})
|
||||
|
||||
if postID != "" {
|
||||
db = db.Where("post_id = ?", postID)
|
||||
@@ -36,7 +40,18 @@ func GetComments(c *gin.Context) {
|
||||
var comments []models.Comment
|
||||
db.Order("created_at DESC").Find(&comments)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": comments})
|
||||
// Comment.Post 在 JSON 中被隐藏,额外附加文章标题和别名供前端展示
|
||||
type commentWithPost struct {
|
||||
models.Comment
|
||||
PostTitle string `json:"post_title"`
|
||||
PostSlug string `json:"post_slug"`
|
||||
}
|
||||
list := make([]commentWithPost, 0, len(comments))
|
||||
for _, cm := range comments {
|
||||
list = append(list, commentWithPost{cm, cm.Post.Title, cm.Post.Slug})
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": list})
|
||||
}
|
||||
|
||||
// 创建评论
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"goblog/config"
|
||||
"goblog/database"
|
||||
"goblog/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// 站点基础设置键名
|
||||
const (
|
||||
OptionSiteName = "site_name"
|
||||
OptionSiteDesc = "site_desc"
|
||||
OptionShowComments = "show_comments" // "1" 显示评论 / "0" 隐藏评论
|
||||
OptionCommentPageSize = "comments_per_page" // 前台评论每页条数
|
||||
)
|
||||
|
||||
// GetSettings 获取站点基础设置(管理员)
|
||||
func GetSettings(c *gin.Context) {
|
||||
cfg := config.GetGlobalConfig()
|
||||
if cfg == nil {
|
||||
cfg = config.Load()
|
||||
}
|
||||
|
||||
opts, err := models.GetOptionsByUser(database.DB, 0)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "读取设置失败"})
|
||||
return
|
||||
}
|
||||
|
||||
// 默认值:站点信息取配置文件,评论默认显示、每页 10 条
|
||||
settings := map[string]string{
|
||||
OptionSiteName: cfg.App.Name,
|
||||
OptionSiteDesc: cfg.App.Description,
|
||||
OptionShowComments: "1",
|
||||
OptionCommentPageSize: "10",
|
||||
}
|
||||
for name := range settings {
|
||||
if v, ok := opts[name]; ok && v != "" {
|
||||
settings[name] = v
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"data": settings})
|
||||
}
|
||||
|
||||
// UpdateSettingsRequest 更新站点基础设置请求
|
||||
type UpdateSettingsRequest struct {
|
||||
SiteName string `json:"site_name" binding:"required"`
|
||||
SiteDesc string `json:"site_desc"`
|
||||
ShowComments bool `json:"show_comments"`
|
||||
CommentsPerPage int `json:"comments_per_page" binding:"required,min=1,max=100"`
|
||||
}
|
||||
|
||||
// UpdateSettings 保存站点基础设置(管理员)
|
||||
func UpdateSettings(c *gin.Context) {
|
||||
var req UpdateSettingsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
showComments := "0"
|
||||
if req.ShowComments {
|
||||
showComments = "1"
|
||||
}
|
||||
options := map[string]string{
|
||||
OptionSiteName: req.SiteName,
|
||||
OptionSiteDesc: req.SiteDesc,
|
||||
OptionShowComments: showComments,
|
||||
OptionCommentPageSize: strconv.Itoa(req.CommentsPerPage),
|
||||
}
|
||||
|
||||
if err := models.BatchSetOptions(database.DB, 0, options); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存设置失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "设置已保存"})
|
||||
}
|
||||
+69
-25
@@ -122,12 +122,11 @@ func IndexView(c *gin.Context) {
|
||||
siteName := cfg.App.Name
|
||||
siteDesc := cfg.App.Description
|
||||
|
||||
var option models.Option
|
||||
if err := database.DB.Where("name = ? AND user_id = ?", "site_name", 0).First(&option).Error; err == nil {
|
||||
siteName = option.Value
|
||||
if v, err := models.GetOptionValue(database.DB, "site_name", 0); err == nil {
|
||||
siteName = v
|
||||
}
|
||||
if err := database.DB.Where("name = ? AND user_id = ?", "site_desc", 0).First(&option).Error; err == nil {
|
||||
siteDesc = option.Value
|
||||
if v, err := models.GetOptionValue(database.DB, "site_desc", 0); err == nil {
|
||||
siteDesc = v
|
||||
}
|
||||
|
||||
data := IndexData{
|
||||
@@ -164,6 +163,10 @@ type PostDetailData struct {
|
||||
Year int
|
||||
PrevPost *models.Post // 上一篇
|
||||
NextPost *models.Post // 下一篇
|
||||
// 评论展示设置与分页
|
||||
ShowComments bool
|
||||
CommentPage int
|
||||
CommentTotalPages int
|
||||
// Sidebar 数据
|
||||
Categories []models.Category
|
||||
Tags []models.Tag
|
||||
@@ -180,14 +183,17 @@ func PostView(c *gin.Context) {
|
||||
slug := c.Param("slug")
|
||||
|
||||
var post models.Post
|
||||
err := database.DB.Preload("Category").Preload("Tags").Preload("Author").
|
||||
Preload("Comments", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("status = ? AND parent_id IS NULL", "approved").Preload("Children", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("status = ?", "approved")
|
||||
})
|
||||
}).
|
||||
Where("slug = ? AND status = ?", slug, "published").
|
||||
First(&post).Error
|
||||
query := database.DB.Preload("Category").Preload("Tags").Preload("Author").
|
||||
Where("status = ?", "published").
|
||||
Session(&gorm.Session{}) // 创建可安全复用的查询会话,避免两次查询条件叠加
|
||||
|
||||
err := query.Where("slug = ?", slug).First(&post).Error
|
||||
// slug 未命中时,若参数为纯数字则回退按文章 ID 查询(兼容 /post/17 形式的链接)
|
||||
if err != nil {
|
||||
if id, convErr := strconv.Atoi(slug); convErr == nil {
|
||||
err = query.Where("id = ?", id).First(&post).Error
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
c.HTML(http.StatusNotFound, "index", gin.H{
|
||||
@@ -209,6 +215,43 @@ func PostView(c *gin.Context) {
|
||||
var commentCount int64
|
||||
database.DB.Model(&models.Comment{}).Where("post_id = ? AND status = ?", post.ID, "approved").Count(&commentCount)
|
||||
|
||||
// 读取评论展示设置
|
||||
showComments := true
|
||||
if v, optErr := models.GetOptionValue(database.DB, OptionShowComments, 0); optErr == nil {
|
||||
showComments = v != "0"
|
||||
}
|
||||
commentPageSize := 10
|
||||
if v, optErr := models.GetOptionValue(database.DB, OptionCommentPageSize, 0); optErr == nil {
|
||||
if n, convErr := strconv.Atoi(v); convErr == nil && n > 0 {
|
||||
commentPageSize = n
|
||||
}
|
||||
}
|
||||
|
||||
// 顶级评论分页查询(回复随父评论一起展示,不单独分页)
|
||||
commentPage, _ := strconv.Atoi(c.DefaultQuery("cpage", "1"))
|
||||
if commentPage < 1 {
|
||||
commentPage = 1
|
||||
}
|
||||
var comments []models.Comment
|
||||
commentTotalPages := 0
|
||||
if showComments {
|
||||
var topCount int64
|
||||
database.DB.Model(&models.Comment{}).
|
||||
Where("post_id = ? AND status = ? AND parent_id IS NULL", post.ID, "approved").
|
||||
Count(&topCount)
|
||||
commentTotalPages = int(math.Ceil(float64(topCount) / float64(commentPageSize)))
|
||||
if commentTotalPages > 0 && commentPage > commentTotalPages {
|
||||
commentPage = commentTotalPages
|
||||
}
|
||||
database.DB.Where("post_id = ? AND status = ? AND parent_id IS NULL", post.ID, "approved").
|
||||
Preload("Children", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Where("status = ?", "approved")
|
||||
}).
|
||||
Order("created_at ASC").
|
||||
Offset((commentPage - 1) * commentPageSize).Limit(commentPageSize).
|
||||
Find(&comments)
|
||||
}
|
||||
|
||||
// 获取上一篇(发布时间更早的文章)
|
||||
var prevPost models.Post
|
||||
prevErr := database.DB.Where("status = ? AND published_at < ?", "published", post.PublishedAt).
|
||||
@@ -223,12 +266,11 @@ func PostView(c *gin.Context) {
|
||||
siteName := cfg.App.Name
|
||||
siteDesc := cfg.App.Description
|
||||
|
||||
var option models.Option
|
||||
if err := database.DB.Where("name = ? AND user_id = ?", "site_name", 0).First(&option).Error; err == nil {
|
||||
siteName = option.Value
|
||||
if v, err := models.GetOptionValue(database.DB, "site_name", 0); err == nil {
|
||||
siteName = v
|
||||
}
|
||||
if err := database.DB.Where("name = ? AND user_id = ?", "site_desc", 0).First(&option).Error; err == nil {
|
||||
siteDesc = option.Value
|
||||
if v, err := models.GetOptionValue(database.DB, "site_desc", 0); err == nil {
|
||||
siteDesc = v
|
||||
}
|
||||
|
||||
// 加载侧边栏数据
|
||||
@@ -241,9 +283,13 @@ func PostView(c *gin.Context) {
|
||||
Description: post.Summary,
|
||||
Post: post,
|
||||
Pages: pages,
|
||||
Comments: post.Comments,
|
||||
Comments: comments,
|
||||
CommentCount: commentCount,
|
||||
Year: time.Now().Year(),
|
||||
// 评论展示设置与分页
|
||||
ShowComments: showComments,
|
||||
CommentPage: commentPage,
|
||||
CommentTotalPages: commentTotalPages,
|
||||
// Sidebar 数据
|
||||
Categories: categories,
|
||||
Tags: tags,
|
||||
@@ -306,14 +352,12 @@ func PageView(c *gin.Context) {
|
||||
siteName := cfg.App.Name
|
||||
siteDesc := cfg.App.Description
|
||||
|
||||
var option models.Option
|
||||
if err := database.DB.Where("name = ? AND user_id = ?", "site_name", 0).First(&option).Error; err == nil {
|
||||
siteName = option.Value
|
||||
if v, err := models.GetOptionValue(database.DB, "site_name", 0); err == nil {
|
||||
siteName = v
|
||||
}
|
||||
if err := database.DB.Where("name = ? AND user_id = ?", "site_desc", 0).First(&option).Error; err == nil {
|
||||
siteDesc = option.Value
|
||||
if v, err := models.GetOptionValue(database.DB, "site_desc", 0); err == nil {
|
||||
siteDesc = v
|
||||
}
|
||||
|
||||
// 加载侧边栏数据
|
||||
categories, tags, postCount, categoryCount, tagCount := loadSidebarData()
|
||||
|
||||
|
||||
+3
-6
@@ -226,12 +226,9 @@ func GetOptionsByUser(db *gorm.DB, userID uint) (map[string]string, error) {
|
||||
func BatchSetOptions(db *gorm.DB, userID uint, options map[string]string) error {
|
||||
return db.Transaction(func(tx *gorm.DB) error {
|
||||
for name, value := range options {
|
||||
opt := Option{
|
||||
Name: name,
|
||||
UserID: userID,
|
||||
Value: value,
|
||||
}
|
||||
if err := tx.Save(&opt).Error; err != nil {
|
||||
// 复合主键在 SQLite 旧表上可能缺失唯一约束,Save 会重复插入,
|
||||
// 改用查找-更新-不存在才插入的方式保证幂等
|
||||
if err := SetOptionValue(tx, name, userID, value); err != nil {
|
||||
slog.Error("保存配置失败", "name", name, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
+5
-1
@@ -95,7 +95,7 @@ func SetupRoutes(r *gin.Engine, cfg interface{}) {
|
||||
admin.DELETE("/pages/:id", handlers.DeletePage)
|
||||
|
||||
// 评论管理
|
||||
admin.GET("/admin/comments", handlers.GetComments)
|
||||
admin.GET("/comments", handlers.GetComments)
|
||||
admin.PUT("/comments/:id/approve", handlers.ApproveComment)
|
||||
admin.PUT("/comments/:id/spam", handlers.MarkSpamComment)
|
||||
admin.DELETE("/comments/:id", handlers.DeleteComment)
|
||||
@@ -107,6 +107,10 @@ func SetupRoutes(r *gin.Engine, cfg interface{}) {
|
||||
admin.PUT("/users/:id/password", handlers.AdminResetPassword)
|
||||
admin.DELETE("/users/:id", handlers.DeleteUser)
|
||||
|
||||
// 基础设置
|
||||
admin.GET("/settings", handlers.GetSettings)
|
||||
admin.PUT("/settings", handlers.UpdateSettings)
|
||||
|
||||
// 文件管理(图片上传)
|
||||
admin.POST("/upload", handlers.UploadImage)
|
||||
admin.GET("/uploads", handlers.ListUploads)
|
||||
|
||||
+92
-4
@@ -46,6 +46,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// 用户表单
|
||||
document.getElementById('user-form').addEventListener('submit', handleUserSubmit);
|
||||
|
||||
// 基础设置表单
|
||||
document.getElementById('settings-form').addEventListener('submit', handleSettingsSubmit);
|
||||
|
||||
// 封面图上传
|
||||
const coverFileInput = document.getElementById('post-cover-file');
|
||||
if (coverFileInput) {
|
||||
@@ -116,6 +119,9 @@ function showPage(pageName) {
|
||||
case 'themes':
|
||||
loadThemes();
|
||||
break;
|
||||
case 'settings':
|
||||
loadSettings();
|
||||
break;
|
||||
case 'theme-editor':
|
||||
// 主题编辑器由 editTheme() 函数加载
|
||||
break;
|
||||
@@ -486,18 +492,24 @@ async function loadPages() {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载评论
|
||||
async function loadComments() {
|
||||
// 加载评论(postId 为空时加载全部)
|
||||
async function loadComments(postId = '') {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/comments`, { headers: getHeaders() });
|
||||
const url = postId ? `${API_BASE}/admin/comments?post_id=${postId}` : `${API_BASE}/admin/comments`;
|
||||
const res = await fetch(url, { headers: getHeaders() });
|
||||
const data = await res.json();
|
||||
|
||||
// 加载全部评论时刷新文章筛选下拉框
|
||||
if (!postId) {
|
||||
populateCommentPostFilter(data.data || []);
|
||||
}
|
||||
|
||||
const tbody = document.getElementById('comments-list');
|
||||
tbody.innerHTML = data.data?.map(c => `
|
||||
<tr>
|
||||
<td>${c.author}</td>
|
||||
<td>${c.content.substring(0, 50)}${c.content.length > 50 ? '...' : ''}</td>
|
||||
<td>文章 #${c.post_id}</td>
|
||||
<td><a href="/post/${encodeURIComponent(c.post_slug || c.post_id)}" target="_blank">${c.post_title || '文章 #' + c.post_id}</a></td>
|
||||
<td><span class="status-badge status-${c.status}">${c.status}</span></td>
|
||||
<td>${new Date(c.created_at).toLocaleString()}</td>
|
||||
<td>
|
||||
@@ -752,6 +764,82 @@ async function loadUsers() {
|
||||
}
|
||||
}
|
||||
|
||||
// 填充评论筛选下拉框(所有有评论的文章)
|
||||
function populateCommentPostFilter(comments) {
|
||||
const select = document.getElementById('comment-post-filter');
|
||||
if (!select) return;
|
||||
|
||||
// 按文章去重
|
||||
const posts = new Map();
|
||||
comments.forEach(c => {
|
||||
if (!posts.has(c.post_id)) {
|
||||
posts.set(c.post_id, c.post_title || ('文章 #' + c.post_id));
|
||||
}
|
||||
});
|
||||
|
||||
select.innerHTML = '<option value="">全部文章</option>';
|
||||
posts.forEach((title, id) => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = id;
|
||||
opt.textContent = title;
|
||||
opt.title = title; // 悬停显示完整标题
|
||||
select.appendChild(opt);
|
||||
});
|
||||
select.value = '';
|
||||
}
|
||||
|
||||
// 按选中文章过滤评论列表
|
||||
function filterCommentsByPost() {
|
||||
loadComments(document.getElementById('comment-post-filter').value);
|
||||
}
|
||||
|
||||
// 加载基础设置
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/admin/settings`, { headers: getHeaders() });
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
alert(data.error || '加载设置失败');
|
||||
return;
|
||||
}
|
||||
const s = data.data || {};
|
||||
document.getElementById('setting-site-name').value = s.site_name || '';
|
||||
document.getElementById('setting-site-desc').value = s.site_desc || '';
|
||||
document.getElementById('setting-show-comments').checked = s.show_comments !== '0';
|
||||
document.getElementById('setting-comments-per-page').value = s.comments_per_page || '10';
|
||||
} catch (err) {
|
||||
console.error('加载设置失败:', err);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存基础设置
|
||||
async function handleSettingsSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const body = {
|
||||
site_name: document.getElementById('setting-site-name').value.trim(),
|
||||
site_desc: document.getElementById('setting-site-desc').value.trim(),
|
||||
show_comments: document.getElementById('setting-show-comments').checked,
|
||||
comments_per_page: parseInt(document.getElementById('setting-comments-per-page').value, 10)
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/admin/settings`, {
|
||||
method: 'PUT',
|
||||
headers: getHeaders(),
|
||||
body: JSON.stringify(body)
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
alert('设置已保存');
|
||||
} else {
|
||||
alert(data.error || '保存失败');
|
||||
}
|
||||
} catch (err) {
|
||||
alert('保存失败: ' + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
let usersCache = [];
|
||||
|
||||
// 显示新建用户表单
|
||||
|
||||
+33
-1
@@ -24,6 +24,7 @@
|
||||
<a href="#users" class="menu-item" data-page="users">用户管理</a>
|
||||
<a href="#files" class="menu-item" data-page="files">文件管理</a>
|
||||
<a href="#themes" class="menu-item" data-page="themes">主题管理</a>
|
||||
<a href="#settings" class="menu-item" data-page="settings">基础设置</a>
|
||||
</nav>
|
||||
<div class="user-info">
|
||||
<span id="username">Admin</span>
|
||||
@@ -241,6 +242,9 @@
|
||||
<div id="comments-page" class="page hidden">
|
||||
<div class="page-header">
|
||||
<h1>评论管理</h1>
|
||||
<select id="comment-post-filter" class="comment-post-filter" onchange="filterCommentsByPost()">
|
||||
<option value="">全部文章</option>
|
||||
</select>
|
||||
</div>
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
@@ -279,6 +283,34 @@
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- 基础设置 -->
|
||||
<div id="settings-page" class="page hidden">
|
||||
<div class="page-header">
|
||||
<h1>基础设置</h1>
|
||||
</div>
|
||||
<form id="settings-form" class="settings-form">
|
||||
<div class="form-group">
|
||||
<label>站点名称</label>
|
||||
<input type="text" id="setting-site-name" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>站点描述</label>
|
||||
<textarea id="setting-site-desc" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="setting-show-comments">
|
||||
前台显示评论
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>评论每页显示条数</label>
|
||||
<input type="number" id="setting-comments-per-page" min="1" max="100" required>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary">保存设置</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- 文件管理 -->
|
||||
<div id="files-page" class="page hidden">
|
||||
<div class="page-header">
|
||||
@@ -442,6 +474,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/admin/app.js?v=12"></script>
|
||||
<script src="/static/admin/app.js?v=16"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -950,3 +950,53 @@ body {
|
||||
padding-left: 24px;
|
||||
}
|
||||
|
||||
/* 评论管理:文章筛选下拉框(固定宽度,超长标题省略显示) */
|
||||
.comment-post-filter {
|
||||
width: 260px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
background: #fff;
|
||||
font-size: 14px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* 基础设置表单 */
|
||||
.settings-form {
|
||||
max-width: 560px;
|
||||
background: #fff;
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.settings-form label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.settings-form input[type="text"],
|
||||
.settings-form input[type="number"],
|
||||
.settings-form textarea {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.settings-form .checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.settings-form .checkbox-label input[type="checkbox"] {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class="comments-section">
|
||||
{{if .ShowComments}}
|
||||
<section class="comments-section" id="comments">
|
||||
<h3>评论 ({{.CommentCount}})</h3>
|
||||
|
||||
<form class="comment-form" action="/comment" method="POST">
|
||||
@@ -112,5 +113,18 @@
|
||||
<p class="no-comments">暂无评论,来发表第一条评论吧!</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if gt .CommentTotalPages 1}}
|
||||
<div class="pagination comment-pagination">
|
||||
{{if gt .CommentPage 1}}
|
||||
<a href="/post/{{.Post.Slug}}?cpage={{sub .CommentPage 1}}#comments" class="prev">← 上一页</a>
|
||||
{{end}}
|
||||
<span class="page-info">{{.CommentPage}} / {{.CommentTotalPages}}</span>
|
||||
{{if lt .CommentPage .CommentTotalPages}}
|
||||
<a href="/post/{{.Post.Slug}}?cpage={{add .CommentPage 1}}#comments" class="next">下一页 →</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -53,7 +53,8 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class="comments-section">
|
||||
{{if .ShowComments}}
|
||||
<section class="comments-section" id="comments">
|
||||
<h3>评论 ({{.CommentCount}})</h3>
|
||||
|
||||
<form class="comment-form" action="/comment" method="POST">
|
||||
@@ -106,5 +107,18 @@
|
||||
<p class="no-comments">暂无评论,来发表第一条评论吧!</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if gt .CommentTotalPages 1}}
|
||||
<div class="pagination comment-pagination">
|
||||
{{if gt .CommentPage 1}}
|
||||
<a href="/post/{{.Post.Slug}}?cpage={{sub .CommentPage 1}}#comments" class="prev">← 上一页</a>
|
||||
{{end}}
|
||||
<span class="page-info">{{.CommentPage}} / {{.CommentTotalPages}}</span>
|
||||
{{if lt .CommentPage .CommentTotalPages}}
|
||||
<a href="/post/{{.Post.Slug}}?cpage={{add .CommentPage 1}}#comments" class="next">下一页 →</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
@@ -59,7 +59,8 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<section class="comments-section">
|
||||
{{if .ShowComments}}
|
||||
<section class="comments-section" id="comments">
|
||||
<h3>评论 ({{.CommentCount}})</h3>
|
||||
|
||||
<form class="comment-form" action="/comment" method="POST">
|
||||
@@ -112,5 +113,18 @@
|
||||
<p class="no-comments">暂无评论,来发表第一条评论吧!</p>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if gt .CommentTotalPages 1}}
|
||||
<div class="pagination comment-pagination">
|
||||
{{if gt .CommentPage 1}}
|
||||
<a href="/post/{{.Post.Slug}}?cpage={{sub .CommentPage 1}}#comments" class="prev">← 上一页</a>
|
||||
{{end}}
|
||||
<span class="page-info">{{.CommentPage}} / {{.CommentTotalPages}}</span>
|
||||
{{if lt .CommentPage .CommentTotalPages}}
|
||||
<a href="/post/{{.Post.Slug}}?cpage={{add .CommentPage 1}}#comments" class="next">下一页 →</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</section>
|
||||
{{end}}
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user