add: frontend
This commit is contained in:
parent
156b8fbb65
commit
47f6ba2104
@ -1,11 +1,17 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"pcgamedb/crawler"
|
||||
"pcgamedb/db"
|
||||
"pcgamedb/log"
|
||||
"pcgamedb/server/handler"
|
||||
"pcgamedb/server/middleware"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"pcgamedb/docs"
|
||||
|
||||
@ -18,7 +24,65 @@ func initRoute(app *gin.Engine) {
|
||||
AllowAllOrigins: true,
|
||||
}))
|
||||
|
||||
GameInfoGroup := app.Group("/game")
|
||||
initFrontend(app)
|
||||
initApi(app)
|
||||
}
|
||||
|
||||
func initFrontend(app *gin.Engine) {
|
||||
app.Static("/static", "server/static")
|
||||
|
||||
layoutFiles, err := filepath.Glob("server/templates/layouts/*.tmpl")
|
||||
if err != nil {
|
||||
log.Logger.Fatal("Error loading layout templates", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
rootFiles, err := filepath.Glob("server/templates/*.tmpl")
|
||||
if err != nil {
|
||||
log.Logger.Fatal("Error loading root templates", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
app.LoadHTMLFiles(append(layoutFiles, rootFiles...)...)
|
||||
|
||||
app.GET("/", func(ctx *gin.Context) {
|
||||
infos, err := crawler.GetSteam250MonthTop50()
|
||||
if err != nil {
|
||||
ctx.HTML(500, "500.tmpl", err)
|
||||
return
|
||||
}
|
||||
ctx.HTML(200, "index.tmpl", gin.H{
|
||||
"MonthTop": infos,
|
||||
})
|
||||
})
|
||||
|
||||
app.GET("/game/:id", func(ctx *gin.Context) {
|
||||
idStr := ctx.Param("id")
|
||||
id, err := primitive.ObjectIDFromHex(idStr)
|
||||
if err != nil {
|
||||
ctx.HTML(400, "400.tmpl", nil)
|
||||
return
|
||||
}
|
||||
info, err := db.GetGameInfoByID(id)
|
||||
if err != nil {
|
||||
ctx.HTML(500, "500.tmpl", err)
|
||||
return
|
||||
}
|
||||
games, err := db.GetGameItemsByIDs(info.GameIDs)
|
||||
if err != nil {
|
||||
ctx.HTML(500, "500.tmpl", err)
|
||||
return
|
||||
}
|
||||
info.Games = games
|
||||
//TODO: fix this
|
||||
ctx.HTML(200, "game.tmpl", info)
|
||||
})
|
||||
}
|
||||
|
||||
func initApi(app *gin.Engine) {
|
||||
apiGroup := app.Group("/api")
|
||||
|
||||
GameInfoGroup := apiGroup.Group("/game")
|
||||
GameItemGroup := GameInfoGroup.Group("/raw")
|
||||
|
||||
GameItemGroup.GET("/unorganized", handler.GetUnorganizedGameItemsHandler)
|
||||
@ -33,11 +97,11 @@ func initRoute(app *gin.Engine) {
|
||||
GameInfoGroup.GET("/id/:id", handler.GetGameInfoByIDHandler)
|
||||
GameInfoGroup.DELETE("/id/:id", middleware.Auth(), handler.DeleteGameInfoHandler)
|
||||
|
||||
app.GET("/popular/:type", handler.GetPopularGameInfosHandler)
|
||||
app.GET("/healthcheck", handler.HealthCheckHandler)
|
||||
app.GET("/author", handler.GetAllAuthorsHandler)
|
||||
app.POST("/clean", middleware.Auth(), handler.CleanGameHandler)
|
||||
apiGroup.GET("/popular/:type", handler.GetPopularGameInfosHandler)
|
||||
apiGroup.GET("/healthcheck", handler.HealthCheckHandler)
|
||||
apiGroup.GET("/author", handler.GetAllAuthorsHandler)
|
||||
apiGroup.POST("/clean", middleware.Auth(), handler.CleanGameHandler)
|
||||
|
||||
docs.SwaggerInfo.BasePath = "/api"
|
||||
app.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
apiGroup.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
}
|
||||
|
2
server/templates/404.html
Normal file
2
server/templates/404.html
Normal file
@ -0,0 +1,2 @@
|
||||
{{template "base" .}} {{define "title"}}Page Not Found{{end}} {{define
|
||||
"styles"}} {{end}} {{define "content"}} Page Not Found {{end}}
|
2
server/templates/500.html
Normal file
2
server/templates/500.html
Normal file
@ -0,0 +1,2 @@
|
||||
{{template "base" .}} {{define "title"}}Server Error{{end}} {{define "styles"}}
|
||||
{{end}} {{define "content"}} {{ . }} {{end}}
|
189
server/templates/game.html
Normal file
189
server/templates/game.html
Normal file
@ -0,0 +1,189 @@
|
||||
{{template "base" .}} {{define "title"}}{{.Name}} - 游戏详情{{end}} {{define
|
||||
"styles"}}
|
||||
<style>
|
||||
.game-cover {
|
||||
max-height: 400px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.screenshot-gallery {
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
.swiper-slide img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-weight: bold;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.download-card {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.download-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 0.25rem 0.5rem;
|
||||
margin: 0.25rem;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
</style>
|
||||
{{end}} {{define "content"}}
|
||||
<!-- Game Details -->
|
||||
<div class="container py-4">
|
||||
<!-- Basic Info -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-4">
|
||||
{{if .Cover}}
|
||||
<img
|
||||
src="{{.Cover}}"
|
||||
class="img-fluid rounded game-cover"
|
||||
alt="{{.Name}}"
|
||||
/>
|
||||
{{else}}
|
||||
<div
|
||||
class="game-cover bg-secondary d-flex align-items-center justify-content-center rounded"
|
||||
>
|
||||
<span class="text-white">暂无封面</span>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<h1 class="mb-3">{{.Name}}</h1>
|
||||
|
||||
{{if .Aliases}}
|
||||
<div>
|
||||
<span class="info-label">别名:</span>
|
||||
{{range .Aliases}}
|
||||
<span class="tag">{{.}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div>
|
||||
<span class="info-label">开发商:</span>
|
||||
{{range .Developers}}
|
||||
<span class="tag">{{.}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="info-label">发行商:</span>
|
||||
{{range .Publishers}}
|
||||
<span class="tag">{{.}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{if .Languages}}
|
||||
<div>
|
||||
<span class="info-label">支持语言:</span>
|
||||
{{range .Languages}}
|
||||
<span class="tag">{{.}}</span>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}} {{if .Description}}
|
||||
<div>
|
||||
<p>{{.Description}}</p>
|
||||
</div>
|
||||
{{end}} {{if .SteamID}}
|
||||
<div>
|
||||
<a
|
||||
href="https://store.steampowered.com/app/{{.SteamID}}"
|
||||
target="_blank"
|
||||
class="btn btn-primary"
|
||||
>
|
||||
在 Steam 上查看
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Screenshots -->
|
||||
{{if .Screenshots}}
|
||||
<div class="mb-4">
|
||||
<h3 class="mb-3">游戏截图</h3>
|
||||
<div class="swiper screenshot-gallery">
|
||||
<div class="swiper-wrapper">
|
||||
{{range .Screenshots}}
|
||||
<div class="swiper-slide">
|
||||
<img src="{{.}}" alt="游戏截图" />
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<div class="swiper-pagination"></div>
|
||||
<div class="swiper-button-next"></div>
|
||||
<div class="swiper-button-prev"></div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<!-- Download Links -->
|
||||
{{if .Games}}
|
||||
<div class="mb-4">
|
||||
<h3 class="mb-3">下载链接</h3>
|
||||
<div class="row g-4">
|
||||
{{range .Games}}
|
||||
<div class="col-md-6">
|
||||
<div class="card download-card">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{.Name}}</h5>
|
||||
{{if .Size}}
|
||||
<p class="card-text">
|
||||
<small class="text-muted">文件大小:{{.Size}}</small>
|
||||
</p>
|
||||
{{end}} {{if .Author}}
|
||||
<p class="card-text">
|
||||
<small class="text-muted">来源:{{.Author}}</small>
|
||||
</p>
|
||||
{{end}} {{if .Password}}
|
||||
<p class="card-text">解压密码:<code>{{.Password}}</code></p>
|
||||
{{end}}
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<a href="{{.Download}}" class="btn btn-success" target="_blank"
|
||||
>下载</a
|
||||
>
|
||||
<a href="{{.Url}}" class="btn btn-outline-primary" target="_blank"
|
||||
>详情页</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-muted">
|
||||
更新时间:{{.UpdatedAt.Format "2006-01-02 15:04:05"}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}} {{define "scripts"}}
|
||||
<script src="https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.js"></script>
|
||||
<script>
|
||||
const swiper = new Swiper(".screenshot-gallery", {
|
||||
slidesPerView: 1,
|
||||
spaceBetween: 30,
|
||||
loop: true,
|
||||
pagination: {
|
||||
el: ".swiper-pagination",
|
||||
clickable: true,
|
||||
},
|
||||
navigation: {
|
||||
nextEl: ".swiper-button-next",
|
||||
prevEl: ".swiper-button-prev",
|
||||
},
|
||||
});
|
||||
</script>
|
||||
{{end}}
|
90
server/templates/index.html
Normal file
90
server/templates/index.html
Normal file
@ -0,0 +1,90 @@
|
||||
{{template "base" .}} {{define "title"}}GameDB{{end}} {{define "styles"}}
|
||||
<style>
|
||||
.game-card {
|
||||
height: 100%;
|
||||
transition: transform 0.2s;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.game-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.game-cover {
|
||||
height: 200px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.game-description {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.col-lg-5-item {
|
||||
flex: 0 0 20%;
|
||||
max-width: 20%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{{end}} {{define "content"}}
|
||||
<!-- Search Section -->
|
||||
<div class="bg-light py-4">
|
||||
<div class="container">
|
||||
<form action="/search" method="GET">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
name="q"
|
||||
placeholder="搜索游戏..."
|
||||
/>
|
||||
<button class="btn btn-primary" type="submit">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Games Grid -->
|
||||
<div class="container py-4">
|
||||
<h2 class="mb-4">Month Top</h2>
|
||||
<div class="row g-4">
|
||||
{{range .MonthTop}}
|
||||
<div class="col-12 col-sm-6 col-md-4 col-lg-5-item">
|
||||
<a href="/game/{{.ID.Hex}}" class="card game-card">
|
||||
{{if .Cover}}
|
||||
<img src="{{.Cover}}" class="card-img-top game-cover" alt="{{.Name}}" />
|
||||
{{else}}
|
||||
<div
|
||||
class="card-img-top game-cover bg-secondary d-flex align-items-center justify-content-center"
|
||||
>
|
||||
<span class="text-white">暂无图片</span>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">{{.Name}}</h5>
|
||||
<p class="card-text game-description">{{.Description}}</p>
|
||||
{{if .Publishers}}
|
||||
<div class="publishers mb-2">
|
||||
<small class="text-muted">发行商:</small>
|
||||
{{range $index, $publisher := .Publishers}} {{if $index}}, {{end}}
|
||||
<small class="text-muted">{{$publisher}}</small>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}} {{define "scripts"}} {{end}}
|
26
server/templates/layouts/base.html
Normal file
26
server/templates/layouts/base.html
Normal file
@ -0,0 +1,26 @@
|
||||
{{define "base"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{{template "title" .}}</title>
|
||||
<link
|
||||
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.css"
|
||||
/>
|
||||
{{block "styles" .}}{{end}}
|
||||
</head>
|
||||
<body>
|
||||
{{template "header" .}} {{template "content" .}} {{template "footer" .}}
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.js"></script>
|
||||
{{block "scripts" .}}{{end}}
|
||||
</body>
|
||||
</html>
|
||||
{{end}}
|
17
server/templates/layouts/footer.html
Normal file
17
server/templates/layouts/footer.html
Normal file
@ -0,0 +1,17 @@
|
||||
{{define "footer"}}
|
||||
<!-- Footer -->
|
||||
<footer class="bg-dark text-light py-4 mt-5">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>关于我们</h5>
|
||||
<p>这是一个提供游戏信息的平台,帮助玩家发现更多优质游戏。</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-md-end">
|
||||
<h5>联系方式</h5>
|
||||
<a href="https://t.me/bestnite">Telegram</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
{{end}}
|
16
server/templates/layouts/header.html
Normal file
16
server/templates/layouts/header.html
Normal file
@ -0,0 +1,16 @@
|
||||
{{define "header"}}
|
||||
<!-- Header -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a class="navbar-brand" href="/">游戏库</a>
|
||||
<button
|
||||
class="navbar-toggler"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#navbarNav"
|
||||
>
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
{{end}}
|
Loading…
Reference in New Issue
Block a user