增加admin相关页面
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -16,3 +16,4 @@
|
||||
# vendor/
|
||||
|
||||
/.vscode
|
||||
bash.exe.stackdump
|
||||
|
||||
@@ -133,7 +133,8 @@ func GetCurrentTheme() string {
|
||||
return "default"
|
||||
|
||||
}
|
||||
func SetCurrentTheme(theme string) {
|
||||
func SetCurrentTheme(theme string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetJWTSecret 获取 JWT 密钥
|
||||
@@ -144,8 +145,8 @@ func SetCurrentTheme(theme string) {
|
||||
|
||||
// 新增包级函数获取 JWT 密钥
|
||||
func GetJWTSecret() string {
|
||||
if globalConfig == nil {
|
||||
panic("配置未加载,请先调用 LoadConfig")
|
||||
}
|
||||
return globalConfig.JwtSecretKey.SecretKey
|
||||
if globalConfig == nil {
|
||||
//panic("配置未加载,请先调用 LoadConfig")
|
||||
}
|
||||
return globalConfig.JwtSecretKey.SecretKey
|
||||
}
|
||||
|
||||
49
controllers/admin.go
Normal file
49
controllers/admin.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ShowAdminIndexPage 渲染后台首页
|
||||
func ShowAdminIndexPage(c *gin.Context) {
|
||||
// 直接加载 web/admin 目录下的 index.tmpl 模板(需确保文件存在)
|
||||
tpl, err := template.ParseFiles("web/admin/index.tmpl")
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "加载模板失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
err = tpl.Execute(c.Writer, gin.H{
|
||||
"Title": "后台管理首页",
|
||||
})
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "渲染模板失败: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ShowThemeSwitchPage 渲染主题切换页面
|
||||
func ShowThemeSwitchPage(c *gin.Context) {
|
||||
// 假设主题列表存储在固定路径或需要手动维护(示例数据,需根据实际情况修改)
|
||||
themes := []string{"default", "dark", "light"} // 示例主题列表
|
||||
currentTheme := "default" // 示例当前主题(需根据实际存储方式获取)
|
||||
|
||||
// 直接加载 web/admin 目录下的 themes.tmpl 模板(需确保文件存在)
|
||||
tpl, err := template.ParseFiles("web/admin/themes.tmpl")
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "加载模板失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
err = tpl.Execute(c.Writer, gin.H{
|
||||
"CurrentTheme": currentTheme,
|
||||
"Themes": themes,
|
||||
})
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "渲染模板失败: "+err.Error())
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,84 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"go_blog/config"
|
||||
"go_blog/themes"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ListThemes 获取可用主题列表
|
||||
// ListThemes 显示主题管理页面(返回主题列表和当前主题)
|
||||
func ListThemes(c *gin.Context) {
|
||||
// 从主题管理器获取当前主题
|
||||
// 从上下文中获取主题管理器
|
||||
tm, exists := c.Get("ThemeManager")
|
||||
if !exists {
|
||||
c.JSON(500, gin.H{"error": "主题管理器未找到"})
|
||||
c.String(http.StatusInternalServerError, "主题管理器未找到")
|
||||
return
|
||||
}
|
||||
themeManager, ok := tm.(*themes.ThemeManager)
|
||||
if !ok {
|
||||
c.String(http.StatusInternalServerError, "主题管理器类型错误")
|
||||
return
|
||||
}
|
||||
themeManager := tm.(*themes.ThemeManager)
|
||||
|
||||
// 读取 web/themes 目录下的所有主题文件夹
|
||||
themesDir := "web/themes"
|
||||
entries, err := os.ReadDir(themesDir)
|
||||
// 获取可用主题列表(读取 web/themes 目录下的所有子目录)
|
||||
entries, err := themeManager.GetAvailableThemes() // 假设 ThemeManager 新增获取主题列表方法
|
||||
if err != nil {
|
||||
c.JSON(500, gin.H{"error": "读取主题目录失败: " + err.Error()})
|
||||
c.String(http.StatusInternalServerError, "读取主题目录失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
var themeList []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
themeList = append(themeList, entry.Name())
|
||||
}
|
||||
// 渲染管理页面模板
|
||||
tpl := themeManager.GetTemplate("admin/themes") // 对应 templates/admin/themes.tmpl
|
||||
if tpl == nil {
|
||||
c.String(http.StatusInternalServerError, "模板 'admin/themes' 未找到")
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, gin.H{
|
||||
"current": themeManager.CurrentTheme(),
|
||||
"themes": themeList,
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
err = tpl.Execute(c.Writer, gin.H{
|
||||
"CurrentTheme": themeManager.CurrentTheme(),
|
||||
"Themes": entries,
|
||||
})
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "渲染模板失败: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// SwitchTheme 处理主题切换请求
|
||||
func SwitchTheme(c *gin.Context) {
|
||||
// 从上下文中获取主题管理器
|
||||
tm, exists := c.Get("ThemeManager")
|
||||
if !exists {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "主题管理器未找到"})
|
||||
return
|
||||
}
|
||||
themeManager, ok := tm.(*themes.ThemeManager)
|
||||
if !ok {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "主题管理器类型错误"})
|
||||
return
|
||||
}
|
||||
|
||||
// 获取前端提交的主题名称
|
||||
newTheme := c.PostForm("theme")
|
||||
if newTheme == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "主题名称不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
// 加载新主题
|
||||
if err := themeManager.LoadTheme(newTheme); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "加载主题失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// 持久化主题配置(假设 config 包支持保存到配置文件)
|
||||
if err := config.SetCurrentTheme(newTheme); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存主题配置失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"status": "主题切换成功", "current_theme": newTheme})
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@ import (
|
||||
"go_blog/pkg/jwt"
|
||||
"go_blog/pkg/util"
|
||||
"go_blog/serializers"
|
||||
"go_blog/themes"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
@@ -43,6 +47,24 @@ func UsersLoginHandler(ctx *gin.Context) {
|
||||
response.ServerError("生成令牌失败: " + err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 添加 Authorization 响应头(格式与 auth.go 的 AuthRequired 方法一致)
|
||||
ctx.Header("Authorization", "Bearer " + token)
|
||||
|
||||
// 表单提交场景:设置Cookie并跳转(需配合前端使用Cookie存储JWT)
|
||||
if ctx.ContentType() == "application/x-www-form-urlencoded" {
|
||||
http.SetCookie(ctx.Writer, &http.Cookie{
|
||||
Name: "token",
|
||||
Value: token,
|
||||
Path: "/",
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
HttpOnly: true, // 防止XSS
|
||||
})
|
||||
ctx.Redirect(http.StatusFound, "/admin/index")
|
||||
return
|
||||
}
|
||||
|
||||
// API请求场景:返回JSON
|
||||
data, _ := util.PrecisionLost(user)
|
||||
data["token"] = token
|
||||
response.Response(data, nil)
|
||||
@@ -104,12 +126,12 @@ func UsersSetPwdHandler(ctx *gin.Context) {
|
||||
response := Response{Ctx: ctx}
|
||||
|
||||
// 从上下文中获取用户(替换原 jwt.AssertUser 调用)
|
||||
user, exists := ctx.Get("user")
|
||||
ctxuser, exists := ctx.Get("user")
|
||||
if !exists {
|
||||
response.Unauthenticated("未验证登录")
|
||||
return
|
||||
}
|
||||
currentUser, ok := user.(*models.Account)
|
||||
currentUser, ok := ctxuser.(*models.Account)
|
||||
if !ok {
|
||||
response.ServerError("用户类型错误")
|
||||
return
|
||||
@@ -155,7 +177,57 @@ func UsersListHandler(ctx *gin.Context) {
|
||||
pager.Total = int(totalCount) // 正确设置总数
|
||||
|
||||
// 分页查询
|
||||
models.DB.Offset(pager.OffSet()).Limit(pager.PageSize).Find(&users)
|
||||
// 由于 pager.OffSet 是 int 类型,直接使用该变量,无需调用函数
|
||||
models.DB.Offset(pager.OffSet).Limit(pager.PageSize).Find(&users)
|
||||
pager.GetPager()
|
||||
response.Response(users, pager)
|
||||
}
|
||||
|
||||
// ShowLoginPage 渲染登录页面
|
||||
func ShowLoginPage(c *gin.Context) {
|
||||
// 直接加载 web/admin 目录下的 login.tmpl 模板(需确保文件存在)
|
||||
tpl, err := template.ParseFiles("web/admin/login.tmpl")
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "加载模板失败: "+err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusOK)
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
err = tpl.Execute(c.Writer, gin.H{
|
||||
"Title": "用户登录",
|
||||
})
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "渲染模板错误: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// ShowRegisterPage 渲染注册页面
|
||||
func ShowRegisterPage(c *gin.Context) {
|
||||
tm, exists := c.Get("ThemeManager")
|
||||
if !exists {
|
||||
c.String(http.StatusInternalServerError, "Theme manager not found")
|
||||
return
|
||||
}
|
||||
themeManager, ok := tm.(*themes.ThemeManager)
|
||||
if !ok {
|
||||
c.String(http.StatusInternalServerError, "Invalid theme manager type")
|
||||
return
|
||||
}
|
||||
|
||||
// 假设主题中存在 register.tmpl 模板(或使用后台固定模板)
|
||||
tpl := themeManager.GetTemplate("register")
|
||||
if tpl == nil {
|
||||
c.String(http.StatusInternalServerError, "Template 'register' not found in current theme. Make sure 'register.html' or 'register.tmpl' exists.")
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusOK)
|
||||
c.Header("Content-Type", "text/html; charset=utf-8")
|
||||
err := tpl.Execute(c.Writer, gin.H{
|
||||
"Title": "用户注册",
|
||||
})
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error rendering template: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go_blog/config"
|
||||
"go_blog/models"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -15,7 +14,8 @@ import (
|
||||
)
|
||||
|
||||
// 定义 JWT 密钥(从配置文件读取)
|
||||
var jwtSecret = []byte(config.GetJWTSecret())
|
||||
// var jwtSecret = []byte(config.GetJWTSecret())
|
||||
var jwtSecret = []byte("your-hardcoded-secret-key")
|
||||
|
||||
// CustomClaims 自定义 JWT 载荷(包含用户 ID)
|
||||
type CustomClaims struct {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"go_blog/controllers"
|
||||
"go_blog/models"
|
||||
"go_blog/pkg/jwt"
|
||||
"go_blog/serializers"
|
||||
"go_blog/themes" // <-- 确保导入 themes 包
|
||||
"log"
|
||||
@@ -15,6 +16,31 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// 新增:自定义后台认证中间件(处理页面重定向)
|
||||
func CheckAdminAuth() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
currentPath := c.Request.URL.Path
|
||||
user, _ := c.Get("user") // 从jwt中间件获取用户信息
|
||||
|
||||
// 已登录状态访问登录/注册页:重定向到后台首页
|
||||
if (currentPath == "/admin/login" || currentPath == "/admin/register") && user != nil {
|
||||
c.Redirect(http.StatusFound, "/admin/index")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// 未登录状态访问非登录/注册页:重定向到登录页
|
||||
if (currentPath != "/admin/login" && currentPath != "/admin/register") && user == nil {
|
||||
c.Redirect(http.StatusFound, "/admin/login")
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func RegisterRoutes(r *gin.Engine) {
|
||||
|
||||
// 注册WebSocket路由
|
||||
@@ -106,34 +132,34 @@ func RegisterRoutes(r *gin.Engine) {
|
||||
|
||||
r.GET("/ws", controllers.WebSocketHandler)
|
||||
r.POST("/content", controllers.CreateContentHandler)
|
||||
r.POST("/login", controllers.UsersLoginHandler)
|
||||
r.POST("/register", controllers.UsersRegisterHandler)
|
||||
r.POST("/setinfo", controllers.UsersSetInfoHandler)
|
||||
r.POST("/setpwd", controllers.UsersSetPwdHandler)
|
||||
// Frontend routes (dynamic themes)
|
||||
r.GET("/", controllers.Home)
|
||||
r.GET("/post/:id", controllers.ShowPost)
|
||||
|
||||
// Admin panel(取消注释并完善)
|
||||
admin := r.Group("/admin", jwt.AuthRequired()) // 新增中间件
|
||||
admin := r.Group("/admin")
|
||||
admin.Use(CheckAdminAuth()) // 使用自定义重定向中间件
|
||||
{
|
||||
admin.GET("/themes", controllers.ListThemes)
|
||||
admin.POST("/themes/switch", controllers.SwitchTheme)
|
||||
// 无需认证的公开路由(登录/注册)
|
||||
admin.GET("/login", controllers.ShowLoginPage)
|
||||
admin.GET("/register", controllers.ShowRegisterPage)
|
||||
admin.POST("/login", controllers.UsersLoginHandler)
|
||||
admin.POST("/register", controllers.UsersRegisterHandler)
|
||||
|
||||
// 需要认证的路由组(使用jwt中间件验证令牌)
|
||||
authAdmin := admin.Group("", jwt.AuthRequired())
|
||||
{
|
||||
authAdmin.GET("/index", controllers.ShowAdminIndexPage) // 后台首页
|
||||
authAdmin.GET("/themes", controllers.ListThemes) // 主题列表
|
||||
authAdmin.GET("/themes/switch", controllers.ShowThemeSwitchPage) // 新增:主题切换页面
|
||||
authAdmin.POST("/themes/switch", controllers.SwitchTheme) // 原有:处理主题切换提交
|
||||
authAdmin.POST("/setinfo", controllers.UsersSetInfoHandler)
|
||||
authAdmin.POST("/setpwd", controllers.UsersSetPwdHandler)
|
||||
}
|
||||
}
|
||||
|
||||
// Static files for themes
|
||||
r.StaticFS("/themes", http.Dir("web/themes"))
|
||||
}
|
||||
func getUserInfo() models.User {
|
||||
user := models.User{
|
||||
Name: "user",
|
||||
Gender: "male",
|
||||
Age: 18,
|
||||
Password: "nothings",
|
||||
PasswordHash: []byte("nothings"),
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func esSSE(c *gin.Context) {
|
||||
w := c.Writer
|
||||
@@ -156,9 +182,8 @@ func esSSE(c *gin.Context) {
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
_, err := fmt.Fprintf(w, "id: aaa\ndata: %s\n\n", "dsdf")
|
||||
_, er1r := fmt.Fprintf(w, "event: connecttime\ndata: %s\n\n", "connecttime")
|
||||
if err != nil || er1r != nil {
|
||||
_, err := fmt.Fprintf(w, "event: connecttime\ndata: %s\n\n", "connecttime")
|
||||
if err != nil {
|
||||
print("error", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>主题管理</title>
|
||||
<style>
|
||||
.container { max-width: 800px; margin: 20px auto; padding: 20px; }
|
||||
.theme-list { margin-top: 20px; }
|
||||
.theme-item { padding: 10px; border: 1px solid #eee; margin: 5px 0; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>主题管理</h1>
|
||||
<p>当前主题:<strong>{{.CurrentTheme}}</strong></p>
|
||||
|
||||
<div class="theme-list">
|
||||
<h3>可选主题</h3>
|
||||
<form action="/admin/themes/switch" method="post">
|
||||
<select name="theme" style="padding: 8px; width: 200px;">
|
||||
{{range .Themes}}
|
||||
<option value="{{.}}" {{if eq . $.CurrentTheme}}selected{{end}}>{{.}}</option>
|
||||
{{end}}
|
||||
</select>
|
||||
<button type="submit" style="padding: 8px 15px; margin-left: 10px;">切换主题</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,53 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Content Creation Form</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Create New Content</h1>
|
||||
<form id="contentForm">
|
||||
<label for="title">Title:</label>
|
||||
<input type="text" id="title" name="title" required><br>
|
||||
|
||||
<label for="slug">Slug:</label>
|
||||
<input type="text" id="slug" name="slug" required><br>
|
||||
|
||||
<label for="text">Text:</label>
|
||||
<textarea id="text" name="text" required></textarea><br>
|
||||
|
||||
<button type="submit">Create Content</button>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.getElementById('contentForm').addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const title = document.getElementById('title').value;
|
||||
const slug = document.getElementById('slug').value;
|
||||
const text = document.getElementById('text').value;
|
||||
|
||||
fetch('/content', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ title, slug, text })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Content created:', data);
|
||||
alert('Content created successfully!');
|
||||
document.getElementById('title').value = '';
|
||||
document.getElementById('slug').value = '';
|
||||
document.getElementById('text').value = '';
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error creating content:', error);
|
||||
alert('An error occurred while creating the content.');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,193 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ .Title}}</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style type="text/css">
|
||||
body{
|
||||
background: #f8f8f8;
|
||||
}
|
||||
.layui-main {
|
||||
width: 1140px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.container {
|
||||
width: 1170px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.header .layui-nav {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
}
|
||||
.post-list {
|
||||
width: 75%;
|
||||
float: left;
|
||||
display: block;
|
||||
}
|
||||
.list-card {
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
padding: 20px 20px 10px 20px;
|
||||
position: relative;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 10px;
|
||||
height: 200px;
|
||||
}
|
||||
.sidebar {
|
||||
float: left;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.header {}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
|
||||
window.addEventListener('load', (event) => {
|
||||
|
||||
var evsrc = new EventSource("http://localhost:8090/events");
|
||||
evsrc.onmessage = function (ev) {
|
||||
console.log("readyStateOnmessage = " + ev.currentTarget.readyState);
|
||||
console.log("getdata = " + ev.data + "lastEventId = " + ev.lastEventId);
|
||||
//document.getElementById("log")
|
||||
//.insertAdjacentHTML("beforeend", "<li>" + ev.data + "</li>");
|
||||
}
|
||||
evsrc.onopen = function (event) {
|
||||
console.log("Connection open ...");
|
||||
};
|
||||
evsrc.onerror = function (ev) {
|
||||
console.log("readyStateOnError = " + ev.currentTarget.readyState);
|
||||
//evsrc.close();
|
||||
}
|
||||
|
||||
evsrc.addEventListener('connecttime', function (event) {
|
||||
console.log("Start time: " + event.data );
|
||||
}, false);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>SSE test</h1>
|
||||
<div>
|
||||
<ul id="log">
|
||||
</ul>
|
||||
</div>
|
||||
<div class="header">
|
||||
<div class="layui-main">
|
||||
<h1><a class="logo" href="https://www.hanxiaonuan.cn/">韩小暖的博客</a></h1>
|
||||
</div>
|
||||
<ul class="layui-nav">
|
||||
<li class="layui-nav-item layui-hide-xs layui-this">
|
||||
<a href="https://www.hanxiaonuan.cn/">首页</a>
|
||||
</li>
|
||||
|
||||
<li class="layui-nav-item layui-hide-xs ">
|
||||
<a href="https://www.hanxiaonuan.cn/start-page.html" title="关于">关于</a>
|
||||
</li>
|
||||
|
||||
<span class="layui-nav-bar"></span></ul>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<div class="post-list">
|
||||
{{ range .Items }}
|
||||
<div class="list-card">
|
||||
<div><a href="/post/{{ .Cid }}">
|
||||
<h1>Title: {{ .Title }}</h1>
|
||||
</a>
|
||||
<p> Slug: {{ .Slug }}</p>
|
||||
<p>Text: {{ .Text }}</p>
|
||||
<div>Created: {{ .Created }},</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="page-navigator">
|
||||
共 {{ .Total }} 条,每页 {{ .PageSize }} 条,当前第 {{ .Page }} 页</div>
|
||||
<div class="pagination">
|
||||
{{ if .PrevPage }}
|
||||
<a href="/page/{{ .PrevPage }}">上一页</a>
|
||||
{{ end }}
|
||||
{{ if .NextPage }}
|
||||
<a href="/page/{{ .NextPage }}">下一页</a>
|
||||
{{ end }}
|
||||
<a href="/">首页</a>
|
||||
<a href="/page/{{ .Total }}">尾页</a>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar">
|
||||
<div class="column">
|
||||
<h3 class="title-sidebar"><i class="layui-icon"></i> 博客信息</h3>
|
||||
<div class="personal-information">
|
||||
<div class="user">
|
||||
<img src="https://www.hanxiaonuan.cn/usr/uploads/2021/07/3991382612.jpg" alt="韩小暖的博客的头像"
|
||||
class="rounded-circle avatar">
|
||||
<div class="p-2">
|
||||
<a class="user-name" target="_blank" href="https://www.hanxiaonuan.cn/">
|
||||
韩小暖的博客</a>
|
||||
<p class="introduction mt-1">这里是小暖的日常记录,欢迎!</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="component">
|
||||
<form class="layui-form" id="search" method="post" action="https://www.hanxiaonuan.cn/"
|
||||
role="search">
|
||||
<div class="layui-inline input">
|
||||
<input type="text" id="s" name="s" class="layui-input" required="" lay-verify="required"
|
||||
placeholder="输入关键字搜索">
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<button class="layui-btn layui-btn-sm layui-btn-primary"><i
|
||||
class="layui-icon"></i></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="column">
|
||||
<h3 class="title-sidebar"><i class="layui-icon"></i> 栏目分类</h3>
|
||||
<ul class="layui-row layui-col-space5">
|
||||
<li class="layui-col-md12 layui-col-xs6"><a
|
||||
href="https://www.hanxiaonuan.cn/category/default/"><i class="layui-icon"></i>
|
||||
默认分类<span class="layui-badge layui-bg-gray">12</span></a></li>
|
||||
<li class="layui-col-md12 layui-col-xs6"><a href="https://www.hanxiaonuan.cn/category/zc/"><i
|
||||
class="layui-icon"></i> 日常随想<span class="layui-badge layui-bg-gray">2</span></a>
|
||||
</li>
|
||||
<li class="layui-col-md12 layui-col-xs6"><a
|
||||
href="https://www.hanxiaonuan.cn/category/%E5%85%B3%E4%BA%8E%E6%88%BF%E5%AD%90/"><i
|
||||
class="layui-icon"></i> 关于房子<span class="layui-badge layui-bg-gray">1</span></a>
|
||||
</li>
|
||||
<li class="layui-col-md12 layui-col-xs6"><a
|
||||
href="https://www.hanxiaonuan.cn/category/%E5%B7%A5%E4%BD%9C%E6%97%A5%E5%BF%97/"><i
|
||||
class="layui-icon"></i> 工作日志<span class="layui-badge layui-bg-gray">0</span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="tags">
|
||||
<h3 class="title-sidebar"><i class="layui-icon"></i>标签云</h3>
|
||||
<div>
|
||||
<a class="layui-btn layui-btn-xs layui-btn-primary" style="color: rgb(231, 229, 26)"
|
||||
href="https://www.hanxiaonuan.cn/tag/%E5%B0%8F%E6%9A%96/" title="小暖">小暖</a>
|
||||
<a class="layui-btn layui-btn-xs layui-btn-primary" style="color: rgb(125, 196, 207)"
|
||||
href="https://www.hanxiaonuan.cn/tag/%E6%84%9F%E6%82%9F/" title="感悟">感悟</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tags">
|
||||
<h3 class="title-sidebar"><i class="layui-icon"></i>系统</h3>
|
||||
<div>
|
||||
<li class="last"><a href="https://www.hanxiaonuan.cn/admin/">进入后台 (admin)</a></li>
|
||||
<li><a href="https://www.hanxiaonuan.cn/action/logout?_=5b09b0e1048bbd45bdddc56fc43667b2">退出</a>
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,43 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>My Blog</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div>
|
||||
{{ range $key, $val := . -}}
|
||||
{{ $key }}:{{ $val }}
|
||||
{{ end -}}
|
||||
|
||||
<h2>
|
||||
<a
|
||||
href="<?php $this->permalink() ?>"><?php $this->title() ?></a>
|
||||
</h2>
|
||||
<ul class="post-meta">
|
||||
<li itemprop="author" itemscope itemtype="http://schema.org/Person"><?php _e('作者: '); ?><a
|
||||
itemprop="name" href="<?php $this->author->permalink(); ?>"
|
||||
rel="author"><?php $this->author(); ?></a></li>
|
||||
<li><?php _e('时间: '); ?>
|
||||
<time datetime="<?php $this->date('c'); ?>" itemprop="datePublished"><?php $this->date(); ?></time>
|
||||
</li>
|
||||
<li><?php _e('分类: '); ?><?php $this->category(','); ?></li>
|
||||
<li itemprop="interactionCount">
|
||||
<a itemprop="discussionUrl"
|
||||
href="<?php $this->permalink() ?>#comments"><?php $this->commentsNum('评论', '1 条评论', '%d 条评论'); ?></a>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="post-content" itemprop="articleBody">
|
||||
<?php $this->content('- 阅读剩余部分 -'); ?>
|
||||
</div>
|
||||
</article>
|
||||
<?php endwhile; ?>
|
||||
|
||||
<?php $this->pageNav('« 前一页', '后一页 »'); ?>
|
||||
</div><!-- end #main-->
|
||||
|
||||
<?php $this->need('sidebar.php'); ?>
|
||||
<?php $this->need('footer.php'); ?>
|
||||
@@ -1,33 +0,0 @@
|
||||
|
||||
<div class="typecho-login-wrap">
|
||||
<div class="typecho-login">
|
||||
<h1><a href="login.html" class="i-logo">登录</a></h1>
|
||||
<form action="login" method="post" name="login" role="form">
|
||||
<p>
|
||||
<label for="name" class="sr-only">{{.user.Name}}</label>
|
||||
<input type="text" id="name" name="username" value="{{.user.Name}}" placeholder="{{.User.Name}}" class="text-l w-100" autofocus />
|
||||
</p>
|
||||
<p>
|
||||
<label for="password" class="sr-only">{{.user.Password}}</label>
|
||||
<input type="password" id="password" name="password" class="text-l w-100" placeholder="{{.user.Password}}" />
|
||||
</p>
|
||||
<p class="submit">
|
||||
<button type="submit" class="btn btn-l w-100 primary">'登录'</button>
|
||||
<input type="hidden" name="referer" value="'referer'" />
|
||||
</p>
|
||||
<p>
|
||||
<label for="remember">
|
||||
<input type="checkbox" name="remember" class="checkbox" value="1" id="remember" /> '下次自动登录'
|
||||
</label>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#name').focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Page 1</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<style type="text/css">
|
||||
.container {
|
||||
width: 1170px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.post-list {
|
||||
width: 75%;
|
||||
float: left;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
float: left;
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.header {}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="layui-col-md9 layui-col-lg9">
|
||||
<div class="title-article">
|
||||
<h1>{{.Title}}</h1>
|
||||
<div class="title-msg">
|
||||
<span><i class="layui-icon"></i> {{.AuthorID}} </span>
|
||||
<span><i class="layui-icon"></i> {{.Created}}</span>
|
||||
<span><i class="layui-icon"></i> {{.CommentsNum}}条</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text" itemprop="articleBody">
|
||||
{{.Text}}
|
||||
</div>
|
||||
<div class="tags-text">
|
||||
<i class="layui-icon"></i>标签:{{.Type}}
|
||||
</div>
|
||||
<div class="copy-text">
|
||||
<div>
|
||||
<p>非特殊说明,本博所有文章均为博主原创。</p>
|
||||
<p class="hidden-xs">如若转载,请注明出处: </p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-text">
|
||||
<div>
|
||||
<span class="layui-badge layui-bg-gray">上一篇</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="layui-badge layui-bg-gray">下一篇</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-text layui-form">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,37 +0,0 @@
|
||||
|
||||
<div class="typecho-login-wrap">
|
||||
<div class="typecho-login">
|
||||
<h1><a href="login.html" class="i-logo">登录</a></h1>
|
||||
<form action="login" method="post" name="login" role="form">
|
||||
<p>
|
||||
<label for="name" class="sr-only">{{.user.Name}}</label>
|
||||
<input type="text" id="name" name="username" value="{{.user.Name}}" placeholder="{{.User.Name}}" class="text-l w-100" autofocus />
|
||||
</p>
|
||||
<p>
|
||||
<label for="password" class="sr-only">{{.user.Password}}</label>
|
||||
<input type="password" id="password" name="password" class="text-l w-100" placeholder="{{.user.Password}}" />
|
||||
</p>
|
||||
<p>
|
||||
<label for="conformPassword" class="sr-only">{{.user.ConformPassword}}</label>
|
||||
<input type="conformPassword" id="conformPassword" name="conformPassword" class="text-l w-100" placeholder="{{.user.ConformPassword}}" />
|
||||
</p>
|
||||
<p class="submit">
|
||||
<button type="submit" class="btn btn-l w-100 primary">'登录'</button>
|
||||
<input type="hidden" name="referer" value="'referer'" />
|
||||
</p>
|
||||
<p>
|
||||
<label for="remember">
|
||||
<input type="checkbox" name="remember" class="checkbox" value="1" id="remember" /> '下次自动登录'
|
||||
</label>
|
||||
</p>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#name').focus();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -200,17 +200,31 @@ func readFile(path string) ([]byte, error) {
|
||||
return os.ReadFile(path)
|
||||
}
|
||||
|
||||
// 获取可用主题列表
|
||||
// ListThemes 获取所有可用主题(目录名)
|
||||
func (m *ThemeManager) ListThemes() ([]string, error) {
|
||||
dirs, err := os.ReadDir(m.themesDir)
|
||||
entries, err := os.ReadDir(m.themesDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("读取主题目录失败: %w", err)
|
||||
}
|
||||
|
||||
var themes []string
|
||||
for _, d := range dirs {
|
||||
if d.IsDir() && isValidTheme(filepath.Join(m.themesDir, d.Name())) {
|
||||
themes = append(themes, d.Name())
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
themes = append(themes, entry.Name())
|
||||
}
|
||||
}
|
||||
return themes, nil
|
||||
}
|
||||
|
||||
// GetAvailableThemes 获取所有可用主题(读取 themesDir 目录下的子目录)
|
||||
func (m *ThemeManager) GetAvailableThemes() ([]string, error) {
|
||||
entries, err := os.ReadDir(m.themesDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取主题目录失败: %w", err)
|
||||
}
|
||||
var themes []string
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() {
|
||||
themes = append(themes, entry.Name())
|
||||
}
|
||||
}
|
||||
return themes, nil
|
||||
|
||||
@@ -23,23 +23,23 @@ func RenderTemplate(c *gin.Context, tmpl string, data gin.H) {
|
||||
// SwitchTheme handles POST requests to switch the current theme
|
||||
// SwitchTheme 处理主题切换请求(修正后)
|
||||
func SwitchTheme(c *gin.Context) {
|
||||
// 从上下文中获取主题管理器(通过 main.go 的 themeMiddleware 注入)
|
||||
tm, exists := c.Get("ThemeManager")
|
||||
if !exists {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "主题管理器未找到"})
|
||||
return
|
||||
}
|
||||
themeManager, ok := tm.(*themes.ThemeManager)
|
||||
if !ok {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "主题管理器类型错误"})
|
||||
return
|
||||
}
|
||||
// 从上下文中获取主题管理器(通过 main.go 的 themeMiddleware 注入)
|
||||
tm, exists := c.Get("ThemeManager")
|
||||
if !exists {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "主题管理器未找到"})
|
||||
return
|
||||
}
|
||||
themeManager, ok := tm.(*ThemeManager)
|
||||
if !ok {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "主题管理器类型错误"})
|
||||
return
|
||||
}
|
||||
|
||||
newTheme := c.PostForm("theme")
|
||||
if err := themeManager.LoadTheme(newTheme); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "加载主题失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
config.SetCurrentTheme(newTheme) // 持久化主题配置(需确保 config 包支持)
|
||||
c.JSON(http.StatusOK, gin.H{"status": "主题切换成功"})
|
||||
newTheme := c.PostForm("theme")
|
||||
if err := themeManager.LoadTheme(newTheme); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "加载主题失败: " + err.Error()})
|
||||
return
|
||||
}
|
||||
config.SetCurrentTheme(newTheme) // 持久化主题配置(需确保 config 包支持)
|
||||
c.JSON(http.StatusOK, gin.H{"status": "主题切换成功"})
|
||||
}
|
||||
|
||||
14
web/admin/index.tmpl
Normal file
14
web/admin/index.tmpl
Normal file
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{.Title}}</h1>
|
||||
<nav>
|
||||
<a href="/admin/themes">主题管理</a>
|
||||
<a href="/admin/setinfo">个人信息</a>
|
||||
</nav>
|
||||
<p>欢迎登录后台管理系统!</p>
|
||||
</body>
|
||||
</html>
|
||||
20
web/admin/login.tmpl
Normal file
20
web/admin/login.tmpl
Normal file
@@ -0,0 +1,20 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{.Title}}web</h1>
|
||||
<form action="/admin/login" method="post">
|
||||
<div>
|
||||
<label>用户名:</label>
|
||||
<input type="text" name="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>密码:</label>
|
||||
<input type="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit">登录</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
24
web/themes/default/templates/register.tmpl
Normal file
24
web/themes/default/templates/register.tmpl
Normal file
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{.Title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{{.Title}}web下</h1>
|
||||
<form action="/register" method="post">
|
||||
<div>
|
||||
<label>用户名:</label>
|
||||
<input type="text" name="username" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>密码:</label>
|
||||
<input type="password" name="password" required>
|
||||
</div>
|
||||
<div>
|
||||
<label>确认密码:</label>
|
||||
<input type="password" name="confirm_password" required>
|
||||
</div>
|
||||
<button type="submit">注册</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user