Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
健杭 徐
intern_project_frontend_backend
Compare Revisions
df96c1081ec2275ac6a5005cbb68527296634782...6f2fe0037df8a76c0d124261f362c3b8aabcd8da
Commits (2)
finish all
· c0f46fb4
健杭 徐
authored
Jun 05, 2025
c0f46fb4
finish all
· 6f2fe003
健杭 徐
authored
Jun 05, 2025
6f2fe003
Expand all
Show whitespace changes
Inline
Side-by-side
comment-system/config/config.yaml
0 → 100644
View file @
6f2fe003
server
:
port
:
8080
timeout
:
5s
database
:
dsn
:
"
user:password@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
comment-system/go.mod
0 → 100644
View file @
6f2fe003
module comment-system
go 1.24.3
require (
github.com/gin-gonic/gin v1.10.1
github.com/lib/pq v1.10.9
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // 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.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
comment-system/go.sum
0 → 100644
View file @
6f2fe003
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
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/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
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.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
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.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
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/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
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/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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
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=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
comment-system/handlers/handlers.go
0 → 100644
View file @
6f2fe003
package
handlers
import
(
"database/sql"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
var
db
*
sql
.
DB
func
InitDB
(
database
*
sql
.
DB
)
{
db
=
database
}
type
Comment
struct
{
ID
int
`json:"id"`
Name
string
`json:"name"`
Content
string
`json:"content"`
CreatedAt
string
`json:"created_at"`
}
type
Response
struct
{
Code
int
`json:"code"`
Msg
string
`json:"msg"`
Data
interface
{}
`json:"data"`
}
// 获取评论
func
GetCommentsHandler
(
c
*
gin
.
Context
)
{
// 参数验证
page
,
err
:=
strconv
.
Atoi
(
c
.
DefaultQuery
(
"page"
,
"1"
))
if
err
!=
nil
||
page
<
1
{
page
=
1
}
size
,
err
:=
strconv
.
Atoi
(
c
.
DefaultQuery
(
"size"
,
"10"
))
if
err
!=
nil
||
size
<
-
1
{
size
=
10
}
// 查询总数
var
total
int
countQuery
:=
"SELECT COUNT(*) FROM comments"
err
=
db
.
QueryRow
(
countQuery
)
.
Scan
(
&
total
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusInternalServerError
,
gin
.
H
{
"code"
:
500
,
"msg"
:
"查询总数失败: "
+
err
.
Error
(),
})
return
}
// 构建查询
query
:=
"SELECT id, name, content, TO_CHAR(created_at, 'YYYY-MM-DD HH24:MI') AS created_at FROM comments"
args
:=
[]
interface
{}{}
// 始终排序
query
+=
" ORDER BY created_at DESC"
// 分页处理
if
size
!=
-
1
{
offset
:=
(
page
-
1
)
*
size
query
+=
" LIMIT $1 OFFSET $2"
args
=
append
(
args
,
size
,
offset
)
}
// 执行查询
rows
,
err
:=
db
.
Query
(
query
,
args
...
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusInternalServerError
,
gin
.
H
{
"code"
:
500
,
"msg"
:
"查询评论失败: "
+
err
.
Error
(),
})
return
}
defer
rows
.
Close
()
// 处理结果
var
comments
[]
Comment
for
rows
.
Next
()
{
var
comment
Comment
if
err
:=
rows
.
Scan
(
&
comment
.
ID
,
&
comment
.
Name
,
&
comment
.
Content
,
&
comment
.
CreatedAt
,
);
err
!=
nil
{
c
.
JSON
(
http
.
StatusInternalServerError
,
gin
.
H
{
"code"
:
500
,
"msg"
:
"解析评论失败: "
+
err
.
Error
(),
})
return
}
comments
=
append
(
comments
,
comment
)
}
// 返回标准响应结构
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"code"
:
0
,
"msg"
:
"success"
,
"data"
:
gin
.
H
{
"total"
:
total
,
"comments"
:
comments
,
},
})
}
// 添加评论
func
AddCommentHandler
(
c
*
gin
.
Context
)
{
var
newComment
Comment
if
err
:=
c
.
ShouldBindJSON
(
&
newComment
);
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
Response
{
Code
:
400
,
Msg
:
"Invalid request"
})
return
}
// 插入数据库
var
id
int
err
:=
db
.
QueryRow
(
"INSERT INTO comments (name, content, created_at) VALUES ($1, $2, $3) RETURNING id"
,
newComment
.
Name
,
newComment
.
Content
,
newComment
.
CreatedAt
,
)
.
Scan
(
&
id
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
Response
{
Code
:
500
,
Msg
:
err
.
Error
()})
return
}
newComment
.
ID
=
id
c
.
JSON
(
http
.
StatusOK
,
Response
{
Code
:
0
,
Msg
:
"Comment added"
,
Data
:
newComment
,
})
}
// 删除评论
func
DeleteCommentHandler
(
c
*
gin
.
Context
)
{
id
,
err
:=
strconv
.
Atoi
(
c
.
Query
(
"id"
))
if
err
!=
nil
||
id
<=
0
{
c
.
JSON
(
http
.
StatusOK
,
Response
{
Code
:
400
,
Msg
:
"Invalid ID"
})
return
}
result
,
err
:=
db
.
Exec
(
"DELETE FROM comments WHERE id = $1"
,
id
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusOK
,
Response
{
Code
:
500
,
Msg
:
err
.
Error
()})
return
}
rowsAffected
,
_
:=
result
.
RowsAffected
()
if
rowsAffected
==
0
{
c
.
JSON
(
http
.
StatusOK
,
Response
{
Code
:
404
,
Msg
:
"Comment not found"
})
return
}
c
.
JSON
(
http
.
StatusOK
,
Response
{
Code
:
0
,
Msg
:
"Comment deleted"
,
Data
:
nil
,
})
}
comment-system/main.go
0 → 100644
View file @
6f2fe003
package
main
import
(
"comment-system/handlers"
"database/sql"
"fmt"
"log"
"github.com/gin-gonic/gin"
_
"github.com/lib/pq"
)
const
(
host
=
"localhost"
port
=
5432
user
=
"comments_user"
password
=
"secure_password"
dbname
=
"comments_db"
)
var
db
*
sql
.
DB
func
main
()
{
psqlInfo
:=
fmt
.
Sprintf
(
"host=%s port=%d user=%s password=%s dbname=%s sslmode=disable"
,
host
,
port
,
user
,
password
,
dbname
)
var
err
error
db
,
err
=
sql
.
Open
(
"postgres"
,
psqlInfo
)
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
defer
db
.
Close
()
err
=
db
.
Ping
()
if
err
!=
nil
{
log
.
Fatal
(
err
)
}
handlers
.
InitDB
(
db
)
createTable
()
var
tableExists
bool
err
=
db
.
QueryRow
(
`
SELECT EXISTS (
SELECT 1
FROM information_schema.tables
WHERE table_name = 'comments'
)
`
)
.
Scan
(
&
tableExists
)
if
err
!=
nil
{
log
.
Fatal
(
"Table check failed: "
,
err
)
}
if
!
tableExists
{
log
.
Println
(
"Table 'comments' not found, creating..."
)
createTable
()
}
else
{
log
.
Println
(
"Table 'comments' already exists"
)
}
router
:=
gin
.
Default
()
router
.
Use
(
func
(
c
*
gin
.
Context
)
{
c
.
Writer
.
Header
()
.
Set
(
"Access-Control-Allow-Origin"
,
"*"
)
c
.
Writer
.
Header
()
.
Set
(
"Access-Control-Allow-Methods"
,
"GET, POST, OPTIONS"
)
c
.
Writer
.
Header
()
.
Set
(
"Access-Control-Allow-Headers"
,
"Content-Type"
)
if
c
.
Request
.
Method
==
"OPTIONS"
{
c
.
AbortWithStatus
(
204
)
return
}
c
.
Next
()
})
router
.
GET
(
"/comment/get"
,
handlers
.
GetCommentsHandler
)
router
.
POST
(
"/comment/add"
,
handlers
.
AddCommentHandler
)
router
.
POST
(
"/comment/delete"
,
handlers
.
DeleteCommentHandler
)
log
.
Println
(
"Server started on :8080"
)
log
.
Fatal
(
router
.
Run
(
":8080"
))
}
// 新增表创建函数
func
createTable
()
{
query
:=
`
CREATE TABLE IF NOT EXISTS comments (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`
_
,
err
:=
db
.
Exec
(
query
)
if
err
!=
nil
{
log
.
Fatalf
(
"创建表失败: %v
\n
请执行以下命令修复:
\n
GRANT USAGE, CREATE ON SCHEMA public TO comments_user;"
,
err
)
}
log
.
Println
(
"表 'comments' 创建成功"
)
}
comment-system/static/.gitignore
0 → 100644
View file @
6f2fe003
# 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?
comment-system/static/README.md
0 → 100644
View file @
6f2fe003
```
cd comment
npm run dev
```
comment-system/static/eslint.config.js
0 → 100644
View file @
6f2fe003
import
js
from
'
@eslint/js
'
import
globals
from
'
globals
'
import
reactHooks
from
'
eslint-plugin-react-hooks
'
import
reactRefresh
from
'
eslint-plugin-react-refresh
'
import
tseslint
from
'
typescript-eslint
'
export
default
tseslint
.
config
(
{
ignores
:
[
'
dist
'
]
},
{
extends
:
[
js
.
configs
.
recommended
,
...
tseslint
.
configs
.
recommended
],
files
:
[
'
**/*.{ts,tsx}
'
],
languageOptions
:
{
ecmaVersion
:
2020
,
globals
:
globals
.
browser
,
},
plugins
:
{
'
react-hooks
'
:
reactHooks
,
'
react-refresh
'
:
reactRefresh
,
},
rules
:
{
...
reactHooks
.
configs
.
recommended
.
rules
,
'
react-refresh/only-export-components
'
:
[
'
warn
'
,
{
allowConstantExport
:
true
},
],
},
},
)
comment-system/static/index.html
0 → 100644
View file @
6f2fe003
<!doctype html>
<html
lang=
"en"
>
<head>
<meta
charset=
"UTF-8"
/>
<link
rel=
"icon"
type=
"image/svg+xml"
href=
"/public/icon.jpg"
/>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
/>
<title>
Xanadu's Comment
</title>
</head>
<body>
<div
id=
"root"
></div>
<script
type=
"module"
src=
"/src/main.tsx"
></script>
<script
src=
"/src/load.js"
></script>
</body>
</html>
comment-system/static/package-lock.json
0 → 100644
View file @
6f2fe003
This diff is collapsed.
Click to expand it.
comment-system/static/package.json
0 → 100644
View file @
6f2fe003
{
"name"
:
"my-app"
,
"private"
:
true
,
"version"
:
"0.0.0"
,
"type"
:
"module"
,
"scripts"
:
{
"dev"
:
"vite"
,
"build"
:
"tsc -b && vite build"
,
"lint"
:
"eslint ."
,
"preview"
:
"vite preview"
},
"dependencies"
:
{
"date-fns"
:
"^4.1.0"
,
"react"
:
"^19.1.0"
,
"react-dom"
:
"^19.1.0"
},
"devDependencies"
:
{
"@eslint/js"
:
"^9.25.0"
,
"@types/react"
:
"^19.1.2"
,
"@types/react-dom"
:
"^19.1.2"
,
"@vitejs/plugin-react-swc"
:
"^3.9.0"
,
"eslint"
:
"^9.25.0"
,
"eslint-plugin-react-hooks"
:
"^5.2.0"
,
"eslint-plugin-react-refresh"
:
"^0.4.19"
,
"globals"
:
"^16.0.0"
,
"typescript"
:
"~5.8.3"
,
"typescript-eslint"
:
"^8.30.1"
,
"vite"
:
"^6.3.5"
}
}
comment-system/static/public/icon.jpg
0 → 100644
View file @
6f2fe003
270 KB
comment-system/static/src/App.css
0 → 100644
View file @
6f2fe003
comment-system/static/src/App.tsx
0 → 100644
View file @
6f2fe003
import
{
format
}
from
"
date-fns
"
;
const
currentTime
=
new
Date
();
const
formattedTime
=
format
(
currentTime
,
'
yyyy-MM-dd HH:mm
'
);
async
function
AddComment
({
name
,
profilePhoto
}:{
name
:
string
,
profilePhoto
:
string
})
{
const
textInput
=
document
.
getElementById
(
'
textInput
'
)
as
HTMLInputElement
;
const
content
=
textInput
.
value
;
if
(
content
===
''
)
{
alert
(
'
Text input cannot be empty
'
)
return
}
if
(
name
===
''
)
{
name
=
'
小黑子
'
}
try
{
const
response
=
await
fetch
(
'
http://localhost:8080/comment/add
'
,
{
method
:
'
POST
'
,
headers
:
{
'
Content-Type
'
:
'
application/json
'
},
body
:
JSON
.
stringify
({
name
,
content
,
created_at
:
formattedTime
})
});
const
result
=
await
response
.
json
();
if
(
result
.
code
!==
0
)
{
alert
(
`操作失败:
${
result
.
msg
}
`
);
throw
new
Error
(
result
.
msg
);
}
const
newComment
=
result
.
data
;
const
NewCommentPart
=
document
.
createElement
(
'
div
'
)
const
UserPhoto
=
document
.
createElement
(
'
img
'
)
UserPhoto
.
className
=
'
comment-avatar
'
UserPhoto
.
src
=
profilePhoto
UserPhoto
.
alt
=
name
UserPhoto
.
style
.
width
=
'
30px
'
UserPhoto
.
style
.
height
=
'
30px
'
UserPhoto
.
style
.
borderRadius
=
'
50%
'
NewCommentPart
.
appendChild
(
UserPhoto
)
const
UserCommentContent
=
document
.
createElement
(
'
div
'
)
UserCommentContent
.
className
=
'
comment-content
'
const
UserName
=
document
.
createElement
(
'
span
'
)
UserName
.
textContent
=
name
UserName
.
className
=
'
comment-meta
'
UserCommentContent
.
appendChild
(
UserName
)
const
UserDate
=
document
.
createElement
(
'
span
'
)
UserDate
.
textContent
=
formattedTime
UserDate
.
className
=
'
comment-date
'
UserCommentContent
.
appendChild
(
UserDate
)
const
UserComment
=
document
.
createElement
(
'
p
'
)
UserComment
.
textContent
=
textInput
.
value
UserComment
.
style
.
marginLeft
=
'
10px
'
textInput
.
value
=
''
;
(
document
.
getElementById
(
'
nameInput
'
)
as
HTMLInputElement
).
value
=
''
;
UserCommentContent
.
appendChild
(
UserComment
)
const
DeleteButtonArea
=
document
.
createElement
(
'
div
'
)
DeleteButtonArea
.
className
=
'
comment-delete
'
const
DeleteButton
=
document
.
createElement
(
'
button
'
)
DeleteButton
.
textContent
=
'
Delete
'
DeleteButton
.
id
=
'
deleteButton
'
DeleteButton
.
onclick
=
async
()
=>
{
const
commentItem
=
DeleteButton
.
closest
(
'
.comment-item
'
)
if
(
commentItem
)
{
commentItem
.
remove
()
try
{
await
fetch
(
`http://localhost:8080/comment/delete?id=
${
newComment
.
id
}
`
,
{
method
:
'
POST
'
});
commentItem
.
remove
();
}
catch
{
alert
(
'
删除失败:
'
);
}
}
}
DeleteButtonArea
.
appendChild
(
DeleteButton
)
UserCommentContent
.
appendChild
(
DeleteButtonArea
)
NewCommentPart
.
appendChild
(
UserCommentContent
)
const
NewComment
=
document
.
createElement
(
'
li
'
)
NewComment
.
className
=
'
comment-item
'
NewComment
.
style
.
listStyle
=
'
none
'
NewComment
.
appendChild
(
NewCommentPart
)
const
CommentList
=
document
.
getElementById
(
'
commentlist
'
)
as
HTMLUListElement
CommentList
.
appendChild
(
NewComment
)
}
catch
(
error
)
{
console
.
error
(
'
添加评论失败:
'
,
error
);
};
}
export
default
AddComment
\ No newline at end of file
comment-system/static/src/App1.tsx
0 → 100644
View file @
6f2fe003
async
function
AddComment
({
name
,
profilePhoto
}:{
name
:
string
,
profilePhoto
:
string
})
{
const
textInput
=
document
.
getElementById
(
'
textInput
'
)
as
HTMLInputElement
;
const
content
=
textInput
.
value
;
if
(
content
===
''
)
{
alert
(
'
Text input cannot be empty
'
)
return
}
if
(
name
===
''
)
{
name
=
'
小黑子
'
}
try
{
const
NewCommentPart
=
document
.
createElement
(
'
div
'
)
const
UserPhoto
=
document
.
createElement
(
'
img
'
)
UserPhoto
.
className
=
'
comment-avatar
'
UserPhoto
.
src
=
profilePhoto
UserPhoto
.
alt
=
name
UserPhoto
.
style
.
width
=
'
30px
'
UserPhoto
.
style
.
height
=
'
30px
'
UserPhoto
.
style
.
borderRadius
=
'
50%
'
NewCommentPart
.
appendChild
(
UserPhoto
)
const
UserCommentContent
=
document
.
createElement
(
'
div
'
)
UserCommentContent
.
className
=
'
comment-content
'
const
UserName
=
document
.
createElement
(
'
span
'
)
UserName
.
textContent
=
name
UserName
.
className
=
'
comment-meta
'
UserCommentContent
.
appendChild
(
UserName
)
const
UserComment
=
document
.
createElement
(
'
p
'
)
UserComment
.
textContent
=
textInput
.
value
UserComment
.
style
.
marginLeft
=
'
10px
'
textInput
.
value
=
''
;
(
document
.
getElementById
(
'
nameInput
'
)
as
HTMLInputElement
).
value
=
''
;
UserCommentContent
.
appendChild
(
UserComment
)
const
DeleteButtonArea
=
document
.
createElement
(
'
div
'
)
DeleteButtonArea
.
className
=
'
comment-delete
'
const
DeleteButton
=
document
.
createElement
(
'
button
'
)
DeleteButton
.
textContent
=
'
Delete
'
DeleteButton
.
id
=
'
deleteButton
'
DeleteButton
.
onclick
=
async
()
=>
{
const
commentItem
=
DeleteButton
.
closest
(
'
.comment-item
'
)
if
(
commentItem
)
{
commentItem
.
remove
()
}
}
DeleteButtonArea
.
appendChild
(
DeleteButton
)
UserCommentContent
.
appendChild
(
DeleteButtonArea
)
NewCommentPart
.
appendChild
(
UserCommentContent
)
const
NewComment
=
document
.
createElement
(
'
li
'
)
NewComment
.
className
=
'
comment-item
'
NewComment
.
style
.
listStyle
=
'
none
'
NewComment
.
appendChild
(
NewCommentPart
)
const
CommentList
=
document
.
getElementById
(
'
commentlist
'
)
as
HTMLUListElement
CommentList
.
appendChild
(
NewComment
)
}
catch
{
alert
(
'
添加评论失败:
'
);
};
}
export
default
AddComment
\ No newline at end of file
comment-system/static/src/date.tsx
0 → 100644
View file @
6f2fe003
import
{
useState
,
useEffect
}
from
'
react
'
;
export
default
function
DateTimeDisplay
()
{
return
(
<
div
className
=
"card"
>
<
Clock
></
Clock
>
<
DateDisplay
></
DateDisplay
>
<
svg
xmlns
=
"http://www.w3.org/2000/svg"
width
=
"1em"
height
=
"1em"
viewBox
=
"0 0 16 16"
stroke-width
=
"0"
fill
=
"currentColor"
stroke
=
"currentColor"
className
=
"moon"
><
path
d
=
"M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"
></
path
><
path
d
=
"M10.794 3.148a.217.217 0 0 1 .412 0l.387 1.162c.173.518.579.924 1.097 1.097l1.162.387a.217.217 0 0 1 0 .412l-1.162.387a1.734 1.734 0 0 0-1.097 1.097l-.387 1.162a.217.217 0 0 1-.412 0l-.387-1.162A1.734 1.734 0 0 0 9.31 6.593l-1.162-.387a.217.217 0 0 1 0-.412l1.162-.387a1.734 1.734 0 0 0 1.097-1.097l.387-1.162zM13.863.099a.145.145 0 0 1 .274 0l.258.774c.115.346.386.617.732.732l.774.258a.145.145 0 0 1 0 .274l-.774.258a1.156 1.156 0 0 0-.732.732l-.258.774a.145.145 0 0 1-.274 0l-.258-.774a1.156 1.156 0 0 0-.732-.732l-.774-.258a.145.145 0 0 1 0-.274l.774-.258c.346-.115.617-.386.732-.732L13.863.1z"
></
path
></
svg
>
</
div
>
)
}
function
Clock
()
{
const
[
time
,
setTime
]
=
useState
(
new
Date
());
useEffect
(()
=>
{
const
timer
=
setInterval
(()
=>
{
setTime
(
new
Date
());
},
1000
);
return
()
=>
clearInterval
(
timer
);
},
[]);
// 创建时间格式化器
const
formatter
=
new
Intl
.
DateTimeFormat
(
'
en-US
'
,
{
hour
:
'
numeric
'
,
minute
:
'
2-digit
'
,
second
:
'
2-digit
'
,
hour12
:
true
});
// 将时间拆分为各个部分
const
parts
=
formatter
.
formatToParts
(
time
);
// 提取时间部分和AM/PM部分
const
timeParts
=
[];
let
period
=
''
;
for
(
const
part
of
parts
)
{
if
(
part
.
type
===
'
dayPeriod
'
)
{
period
=
part
.
value
.
toUpperCase
();
// 转换为大写
}
else
{
timeParts
.
push
(
part
.
value
);
}
}
// 合并时间部分(小时、分钟、秒)
const
timeString
=
timeParts
.
join
(
''
);
return
(
<
p
className
=
'time-text'
>
<
span
className
=
'time-text'
>
{
timeString
}
</
span
>
<
span
className
=
'time-sub-text'
>
{
period
}
</
span
>
</
p
>
);
}
function
DateDisplay
()
{
const
[
date
,
setDate
]
=
useState
(
new
Date
());
useEffect
(()
=>
{
const
timer
=
setInterval
(()
=>
{
setDate
(
new
Date
());
},
1000
);
return
()
=>
clearInterval
(
timer
);
},
[]);
// 获取星期名称
const
weekday
=
date
.
toLocaleDateString
(
'
en-US
'
,
{
weekday
:
'
long
'
});
// 获取月份名称
const
month
=
date
.
toLocaleDateString
(
'
en-US
'
,
{
month
:
'
long
'
});
// 获取日期数字
const
day
=
date
.
getDate
();
// 添加日期序数后缀
interface
GetDayWithSuffix
{
(
day
:
number
):
string
;
}
const
getDayWithSuffix
:
GetDayWithSuffix
=
(
day
:
number
):
string
=>
{
if
(
day
>
3
&&
day
<
21
)
return
`
${
day
}
th`
;
// 11th-13th例外规则
switch
(
day
%
10
)
{
case
1
:
return
`
${
day
}
st`
;
case
2
:
return
`
${
day
}
nd`
;
case
3
:
return
`
${
day
}
rd`
;
default
:
return
`
${
day
}
th`
;
}
};
return
<
p
className
=
'day-text'
>
{
weekday
}
,
{
month
}
{
getDayWithSuffix
(
day
)
}
</
p
>;
}
\ No newline at end of file
comment-system/static/src/index.css
0 → 100644
View file @
6f2fe003
:root
{
/* Enhanced color palette */
--primary-color
:
#6366f1
;
--primary-hover
:
#4f46e5
;
--primary-light
:
#a5b4fc
;
--secondary-color
:
#06b6d4
;
--accent-color
:
#f59e0b
;
--success-color
:
#10b981
;
/* Text colors */
--text-primary
:
#0f172a
;
--text-secondary
:
#475569
;
--text-muted
:
#94a3b8
;
/* Background colors */
--bg-primary
:
#ffffff
;
--bg-secondary
:
#f8fafc
;
--bg-accent
:
#f1f5f9
;
--bg-gradient
:
linear-gradient
(
135deg
,
#667eea
0%
,
#764ba2
100%
);
/* Border and shadows */
--border-color
:
#e2e8f0
;
--border-radius
:
16px
;
--border-radius-sm
:
8px
;
--shadow-sm
:
0
1px
3px
rgba
(
15
,
23
,
42
,
0.08
);
--shadow-md
:
0
4px
16px
rgba
(
15
,
23
,
42
,
0.1
);
--shadow-lg
:
0
8px
32px
rgba
(
15
,
23
,
42
,
0.12
);
--shadow-xl
:
0
20px
64px
rgba
(
15
,
23
,
42
,
0.15
);
/* Transitions */
--transition-fast
:
all
0.15s
cubic-bezier
(
0.4
,
0
,
0.2
,
1
);
--transition-normal
:
all
0.3s
cubic-bezier
(
0.4
,
0
,
0.2
,
1
);
--transition-slow
:
all
0.5s
cubic-bezier
(
0.4
,
0
,
0.2
,
1
);
}
/* Global styles */
*
{
box-sizing
:
border-box
;
}
body
{
font-family
:
'Inter'
,
-apple-system
,
BlinkMacSystemFont
,
'Segoe UI'
,
Roboto
,
sans-serif
;
background
:
var
(
--bg-secondary
);
background-image
:
radial-gradient
(
circle
at
25%
25%
,
rgba
(
99
,
102
,
241
,
0.05
)
0%
,
transparent
50%
),
radial-gradient
(
circle
at
75%
75%
,
rgba
(
6
,
182
,
212
,
0.05
)
0%
,
transparent
50%
);
min-height
:
100vh
;
margin
:
0
;
padding
:
0
;
line-height
:
1.6
;
color
:
var
(
--text-primary
);
}
/* Header styling */
h1
{
background
:
var
(
--bg-gradient
);
-webkit-background-clip
:
text
;
-webkit-text-fill-color
:
transparent
;
background-clip
:
text
;
font-size
:
2.5rem
;
font-weight
:
800
;
text-align
:
center
;
margin
:
2rem
0
;
text-shadow
:
0
2px
4px
rgba
(
0
,
0
,
0
,
0.1
);
animation
:
fadeInDown
0.8s
ease-out
;
}
h3
{
color
:
var
(
--text-secondary
);
font-size
:
1.5rem
;
font-weight
:
600
;
text-align
:
center
;
margin
:
1.5rem
0
;
position
:
relative
;
}
h3
::after
{
content
:
''
;
position
:
absolute
;
bottom
:
-8px
;
left
:
50%
;
transform
:
translateX
(
-50%
);
width
:
60px
;
height
:
3px
;
background
:
var
(
--bg-gradient
);
border-radius
:
2px
;
}
/* Input area styling */
.inputarea
{
background
:
var
(
--bg-primary
);
padding
:
2rem
;
border-radius
:
var
(
--border-radius
);
box-shadow
:
var
(
--shadow-lg
);
width
:
min
(
95%
,
700px
);
margin
:
2rem
auto
;
border
:
1px
solid
var
(
--border-color
);
position
:
relative
;
backdrop-filter
:
blur
(
10px
);
animation
:
slideInUp
0.6s
ease-out
;
display
:
flex
;
flex-direction
:
column
;
gap
:
1.5rem
;
}
.inputarea
::before
{
content
:
''
;
position
:
absolute
;
top
:
0
;
left
:
0
;
right
:
0
;
height
:
4px
;
background
:
var
(
--bg-gradient
);
border-radius
:
var
(
--border-radius
)
var
(
--border-radius
)
0
0
;
}
.inputarea
::after
{
content
:
''
;
position
:
absolute
;
bottom
:
-2rem
;
left
:
50%
;
transform
:
translateX
(
-50%
);
width
:
60%
;
height
:
6px
;
background
:
linear-gradient
(
90deg
,
transparent
,
var
(
--primary-light
),
transparent
);
opacity
:
0.4
;
border-radius
:
3px
;
}
/* Profile section in input area */
.inputarea
>
div
:first-child
{
display
:
flex
;
align-items
:
center
;
gap
:
1rem
;
margin-bottom
:
1rem
;
}
.inputarea
img
{
width
:
48px
;
height
:
48px
;
border-radius
:
50%
;
object-fit
:
cover
;
border
:
3px
solid
var
(
--primary-light
);
box-shadow
:
var
(
--shadow-md
);
transition
:
var
(
--transition-normal
);
}
.inputarea
img
:hover
{
transform
:
scale
(
1.05
);
border-color
:
var
(
--primary-color
);
}
.card
{
width
:
280px
;
height
:
60px
;
background
:
rgb
(
17
,
4
,
134
);
border-radius
:
15px
;
box-shadow
:
rgb
(
0
,
0
,
0
,
0.7
)
5px
5px
10px
,
rgb
(
0
,
0
,
0
,
0.7
)
-5px
0px
10px
;
display
:
flex
;
color
:
white
;
justify-content
:
center
;
position
:
absolute
;
right
:
30px
;
top
:
21px
;
flex-direction
:
column
;
background
:
linear-gradient
(
to
right
,
rgb
(
20
,
30
,
48
),
rgb
(
36
,
59
,
85
));
cursor
:
pointer
;
transition
:
all
0.3s
ease-in-out
;
overflow
:
hidden
;
}
.card
:hover
{
box-shadow
:
rgb
(
0
,
0
,
0
)
5px
5px
10px
,
rgb
(
0
,
0
,
0
)
-5px
0px
10px
;
}
.time-text
{
font-size
:
20px
;
margin-bottom
:
0px
;
margin-left
:
8px
;
font-weight
:
600
;
font-family
:
'Gill Sans'
,
'Gill Sans MT'
,
Calibri
,
'Trebuchet MS'
,
sans-serif
;
}
.time-sub-text
{
font-size
:
15px
;
margin-left
:
5px
;
}
.day-text
{
font-size
:
15px
;
margin-top
:
0px
;
margin-left
:
15px
;
font-weight
:
500
;
font-family
:
'Gill Sans'
,
'Gill Sans MT'
,
Calibri
,
'Trebuchet MS'
,
sans-serif
;
}
.moon
{
font-size
:
20px
;
position
:
absolute
;
right
:
15px
;
top
:
15px
;
transition
:
all
0.3s
ease-in-out
;
}
.card
:hover
>
.moon
{
font-size
:
23px
;
}
/* Input field styling */
.inputarea
input
{
width
:
100%
;
padding
:
1rem
1.25rem
;
border
:
2px
solid
var
(
--border-color
);
border-radius
:
var
(
--border-radius-sm
);
font-size
:
1rem
;
font-family
:
inherit
;
transition
:
var
(
--transition-normal
);
background
:
var
(
--bg-secondary
);
outline
:
none
;
resize
:
none
;
}
.inputarea
input
::placeholder
{
color
:
var
(
--text-muted
);
font-style
:
italic
;
}
.inputarea
input
:focus
{
border-color
:
var
(
--primary-color
);
box-shadow
:
0
0
0
4px
rgba
(
99
,
102
,
241
,
0.1
);
background
:
var
(
--bg-primary
);
transform
:
translateY
(
-1px
);
}
/* Submit button styling */
#submitButton
{
background
:
var
(
--bg-gradient
);
color
:
white
;
padding
:
0.875rem
2rem
;
border
:
none
;
border-radius
:
var
(
--border-radius-sm
);
font-weight
:
600
;
font-size
:
1rem
;
cursor
:
pointer
;
transition
:
var
(
--transition-normal
);
display
:
inline-flex
;
align-items
:
center
;
justify-content
:
center
;
gap
:
0.5rem
;
position
:
relative
;
overflow
:
hidden
;
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
align-self
:
flex-start
;
}
#submitButton
::before
{
content
:
''
;
position
:
absolute
;
top
:
0
;
left
:
-100%
;
width
:
100%
;
height
:
100%
;
background
:
linear-gradient
(
90deg
,
transparent
,
rgba
(
255
,
255
,
255
,
0.2
),
transparent
);
transition
:
var
(
--transition-normal
);
}
#submitButton
:hover
{
transform
:
translateY
(
-2px
);
box-shadow
:
var
(
--shadow-lg
);
}
#submitButton
:hover::before
{
left
:
100%
;
}
#submitButton
:active
{
transform
:
translateY
(
0
);
}
/* Comment list container */
.comment
{
width
:
min
(
95%
,
700px
);
margin
:
0
auto
2rem
;
}
.no-comments
{
text-align
:
center
;
color
:
var
(
--text-muted
);
font-size
:
1.25rem
;
margin-top
:
2rem
;
animation
:
fadeInDown
0.5s
ease-out
;
}
#commentlist
{
padding
:
0
;
margin
:
0
;
list-style
:
none
;
}
/* Individual comment item styling */
.comment-item
{
background
:
var
(
--bg-primary
);
padding
:
1.5rem
;
border-radius
:
var
(
--border-radius
);
box-shadow
:
var
(
--shadow-md
);
margin-bottom
:
1.5rem
;
position
:
relative
;
transition
:
var
(
--transition-normal
);
border-left
:
4px
solid
var
(
--primary-light
);
animation
:
fadeInLeft
0.5s
ease-out
;
}
.comment-item
:hover
{
transform
:
translateY
(
-3px
)
translateX
(
5px
);
box-shadow
:
var
(
--shadow-xl
);
border-left-color
:
var
(
--primary-color
);
}
.comment-item
::before
{
content
:
''
;
position
:
absolute
;
top
:
1.5rem
;
left
:
-2px
;
width
:
4px
;
height
:
24px
;
background
:
var
(
--accent-color
);
border-radius
:
2px
;
opacity
:
0
;
transition
:
var
(
--transition-normal
);
}
.comment-item
:hover::before
{
opacity
:
1
;
}
/* Comment content layout */
.comment-item
>
div
{
display
:
flex
;
gap
:
1rem
;
align-items
:
flex-start
;
}
/* Comment avatar */
.comment-avatar
{
width
:
40px
!important
;
height
:
40px
!important
;
border-radius
:
50%
!important
;
object-fit
:
cover
;
border
:
2px
solid
var
(
--primary-light
)
!important
;
box-shadow
:
var
(
--shadow-sm
)
!important
;
transition
:
var
(
--transition-normal
)
!important
;
flex-shrink
:
0
;
}
.comment-item
:hover
.comment-avatar
{
transform
:
scale
(
1.1
);
border-color
:
var
(
--primary-color
)
!important
;
}
/* Comment content area */
.comment-content
{
flex
:
1
;
margin-left
:
0
!important
;
}
/* Comment meta (username) */
.comment-meta
{
color
:
var
(
--text-secondary
)
!important
;
font-size
:
0.875rem
!important
;
font-weight
:
600
!important
;
margin-bottom
:
0.5rem
!important
;
display
:
flex
!important
;
align-items
:
center
!important
;
gap
:
0.5rem
!important
;
}
.comment-meta
::after
{
content
:
'•'
;
color
:
var
(
--text-muted
);
font-size
:
0.75rem
;
}
.comment-date
{
color
:
var
(
--text-secondary
)
!important
;
font-size
:
0.875rem
!important
;
font-weight
:
600
!important
;
margin-bottom
:
0.5rem
!important
;
display
:
flex
!important
;
align-items
:
center
!important
;
gap
:
0.5rem
!important
;
}
.comment-date
::after
{
content
:
'•'
;
color
:
var
(
--text-muted
);
font-size
:
0.75rem
;
}
/* Comment text */
.comment-content
p
{
color
:
var
(
--text-primary
)
!important
;
margin
:
0
!important
;
line-height
:
1.7
!important
;
padding
:
1rem
!important
;
background
:
var
(
--bg-accent
)
!important
;
border-radius
:
var
(
--border-radius-sm
)
!important
;
position
:
relative
!important
;
border
:
1px
solid
var
(
--border-color
);
margin-left
:
0
!important
;
}
.comment-content
p
::before
{
content
:
''
!important
;
position
:
absolute
!important
;
left
:
-6px
!important
;
top
:
1rem
!important
;
width
:
0
!important
;
height
:
0
!important
;
border-style
:
solid
!important
;
border-width
:
6px
6px
6px
0
!important
;
border-color
:
transparent
var
(
--bg-accent
)
transparent
transparent
!important
;
}
#deleteButton
{
background
:
transparent
;
color
:
var
(
--text-muted
);
border
:
none
;
cursor
:
pointer
;
font-size
:
0.875rem
;
font-weight
:
600
;
transition
:
var
(
--transition-normal
);
}
/* Animations */
@keyframes
fadeInDown
{
from
{
opacity
:
0
;
transform
:
translateY
(
-30px
);
}
to
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
}
@keyframes
slideInUp
{
from
{
opacity
:
0
;
transform
:
translateY
(
30px
);
}
to
{
opacity
:
1
;
transform
:
translateY
(
0
);
}
}
@keyframes
fadeInLeft
{
from
{
opacity
:
0
;
transform
:
translateX
(
-20px
);
}
to
{
opacity
:
1
;
transform
:
translateX
(
0
);
}
}
/* Dark mode support */
@media
(
prefers-color-scheme
:
dark
)
{
:root
{
--text-primary
:
#f1f5f9
;
--text-secondary
:
#cbd5e1
;
--text-muted
:
#64748b
;
--bg-primary
:
#1e293b
;
--bg-secondary
:
#0f172a
;
--bg-accent
:
#334155
;
--border-color
:
#475569
;
--bg-gradient
:
linear-gradient
(
135deg
,
#4f46e5
0%
,
#06b6d4
100%
);
}
body
{
background-image
:
radial-gradient
(
circle
at
25%
25%
,
rgba
(
79
,
70
,
229
,
0.1
)
0%
,
transparent
50%
),
radial-gradient
(
circle
at
75%
75%
,
rgba
(
6
,
182
,
212
,
0.1
)
0%
,
transparent
50%
);
}
.inputarea
input
{
background
:
var
(
--bg-accent
);
}
.inputarea
input
:focus
{
background
:
var
(
--bg-primary
);
}
}
/* Responsive design */
@media
(
max-width
:
768px
)
{
h1
{
font-size
:
2rem
;
}
.inputarea
{
padding
:
1.5rem
;
margin
:
1rem
auto
;
width
:
min
(
95%
,
100%
);
}
.comment
{
width
:
min
(
95%
,
100%
);
}
.comment-item
{
padding
:
1rem
;
}
.comment-item
>
div
{
flex-direction
:
column
;
gap
:
0.75rem
;
}
.comment-avatar
{
align-self
:
flex-start
;
}
}
/* Scrollbar styling */
::-webkit-scrollbar
{
width
:
8px
;
}
::-webkit-scrollbar-track
{
background
:
var
(
--bg-secondary
);
}
::-webkit-scrollbar-thumb
{
background
:
var
(
--primary-light
);
border-radius
:
4px
;
}
::-webkit-scrollbar-thumb:hover
{
background
:
var
(
--primary-color
);
}
\ No newline at end of file
comment-system/static/src/load.js
0 → 100644
View file @
6f2fe003
async
function
loadComments
()
{
try
{
const
response
=
await
fetch
(
'
http://localhost:8080/comment/get
'
);
const
result
=
await
response
.
json
();
// 检查业务状态码
if
(
result
.
code
!==
0
)
{
throw
new
Error
(
result
.
msg
||
'
未知错误
'
);
}
// 正确的数据结构访问
displayComments
(
result
.
data
.
comments
);
}
catch
(
error
)
{
console
.
error
(
'
加载评论错误:
'
,
error
);
alert
(
'
加载评论失败:
'
+
error
.
message
);
}
}
function
displayComments
(
comments
)
{
const
commentsContainer
=
document
.
getElementById
(
'
commentlist
'
);
commentsContainer
.
innerHTML
=
''
;
if
(
!
comments
||
comments
.
length
===
0
)
{
commentsContainer
.
innerHTML
=
'
<div class="no-comments">No Comments Yet</div>
'
;
return
;
}
comments
.
forEach
(
comment
=>
{
const
commentElement
=
document
.
createElement
(
'
li
'
);
commentElement
.
className
=
'
comment-item
'
;
commentElement
.
innerHTML
=
`
<div>
<img src='https://pic4.zhimg.com/v2-bf5f58e7b583cd69ac228db9fdff377f_r.jpg'
class='comment-avatar' alt='
${
comment
.
name
}
' />
<div class='comment-content'>
<div>
<span class='comment-meta'>
${
comment
.
name
}
</span>
<span class='comment-date'>
${
comment
.
created_at
}
</span>
</div>
<p>
${
comment
.
content
}
</p>
<div class='comment-delete'>
<button id = 'deleteButton' onclick="deleteComment(
${
comment
.
id
}
)">Delete</button>
</div>
</div>
</div>
`
;
commentsContainer
.
appendChild
(
commentElement
);
});
}
function
deleteComment
(
commentId
)
{
if
(
!
confirm
(
'
确定要删除这条评论吗?
'
))
{
return
;
}
fetch
(
`http://localhost:8080/comment/delete?id=
${
commentId
}
`
,
{
method
:
'
POST
'
,
})
.
then
(
response
=>
response
.
json
())
.
then
(
result
=>
{
if
(
result
.
code
!==
0
)
{
throw
new
Error
(
result
.
msg
||
'
删除失败
'
);
}
alert
(
'
评论已删除
'
);
loadComments
();
// 重新加载评论列表
})
.
catch
(
error
=>
{
console
.
error
(
'
删除评论错误:
'
,
error
);
alert
(
'
删除评论失败:
'
+
error
.
message
);
});
}
// 页面加载时调用
document
.
addEventListener
(
'
DOMContentLoaded
'
,
()
=>
{
loadComments
();
});
\ No newline at end of file
comment-system/static/src/main.tsx
0 → 100644
View file @
6f2fe003
import
{
StrictMode
}
from
'
react
'
import
{
createRoot
}
from
'
react-dom/client
'
import
AddComment
from
'
./App.tsx
'
import
DateTimeDisplay
from
'
./date.tsx
'
import
'
./index.css
'
const
profilePhoto
:
string
=
'
https://pic4.zhimg.com/v2-bf5f58e7b583cd69ac228db9fdff377f_r.jpg
'
createRoot
(
document
.
getElementById
(
'
root
'
)
!
).
render
(
<
StrictMode
>
<
div
>
<
h1
style
=
{
{
textAlign
:
'
center
'
}
}
>
Welcome To Xanadu`s Comment
</
h1
>
</
div
>
<
Input_Area
/>
<
div
className
=
'comment'
>
<
div
>
<
h3
style
=
{
{
textAlign
:
'
center
'
}
}
>
All Comment
</
h3
>
</
div
>
<
ul
id
=
'commentlist'
></
ul
>
</
div
>
</
StrictMode
>
)
function
Input_Area
()
{
return
(
<
div
className
=
'inputarea'
>
<
img
src
=
{
profilePhoto
}
alt
=
{
'
小黑子
'
}
style
=
{
{
width
:
'
40px
'
,
height
:
'
40px
'
,
borderRadius
:
'
50%
'
}
}
/>
<
DateTimeDisplay
/>
<
input
type
=
"text"
placeholder
=
"Please enter your name"
id
=
"nameInput"
/>
<
input
type
=
"text"
placeholder
=
"Please follow the Community Guidelines"
id
=
"textInput"
onKeyUpCapture
=
{
(
e
)
=>
{
const
name
:
string
=
(
document
.
getElementById
(
'
nameInput
'
)
as
HTMLInputElement
).
value
;
if
(
e
.
key
===
'
Enter
'
)
{
AddComment
({
name
,
profilePhoto
});
}
}
}
/>
<
button
id
=
"submitButton"
onClick
=
{
()
=>
AddComment
({
name
:
(
document
.
getElementById
(
'
nameInput
'
)
as
HTMLInputElement
).
value
,
profilePhoto
:
profilePhoto
})
}
>
Submit
</
button
>
</
div
>
)
}
// Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
\ No newline at end of file
comment-system/static/src/object.ts
0 → 100644
View file @
6f2fe003
class
girlFriend
{
name
:
string
;
age
:
number
;
character
:
string
[];
height
:
number
;
weight
:
number
;
constructor
(
name
:
string
,
age
:
number
,
character
:
string
[],
height
:
number
,
weight
:
number
)
{
this
.
name
=
name
;
this
.
age
=
age
;
this
.
character
=
character
;
this
.
height
=
height
;
this
.
weight
=
weight
;
}
}
const
MyGirlFriend
=
new
girlFriend
(
"
小红
"
,
18
,
[
"
温柔
"
,
"
善良
"
],
160
,
50
);
console
.
log
(
MyGirlFriend
);
\ No newline at end of file
Prev
1
2
Next