优化页面、修复BUG
This commit is contained in:
@@ -272,6 +272,7 @@ func AdminGetAttachments(c *gin.Context) {
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
|
||||
categoryIDStr := c.Query("categoryId")
|
||||
fileType := c.Query("fileType")
|
||||
keyword := c.Query("keyword")
|
||||
|
||||
var categoryID *uint
|
||||
if categoryIDStr != "" {
|
||||
@@ -281,7 +282,7 @@ func AdminGetAttachments(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
attachments, total, err := repositories.GetAttachments(page, pageSize, categoryID, fileType)
|
||||
attachments, total, err := repositories.GetAttachments(page, pageSize, categoryID, fileType, keyword)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
|
||||
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/niangaodev/art-code/models"
|
||||
@@ -41,16 +42,41 @@ func GetEmailSuffixes(c *gin.Context) {
|
||||
|
||||
// AdminGetInquiries 获取咨询列表
|
||||
func AdminGetInquiries(c *gin.Context) {
|
||||
inquiries, err := repositories.GetInquiries()
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "50"))
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 {
|
||||
pageSize = 50
|
||||
}
|
||||
|
||||
filter := repositories.AdminInquiryFilter{
|
||||
Keyword: c.Query("keyword"),
|
||||
StartDate: c.Query("startDate"),
|
||||
EndDate: c.Query("endDate"),
|
||||
Status: -1,
|
||||
}
|
||||
if statusStr := c.Query("status"); statusStr != "" {
|
||||
if v, err := strconv.Atoi(statusStr); err == nil {
|
||||
filter.Status = v
|
||||
}
|
||||
}
|
||||
|
||||
inquiries, total, err := repositories.GetInquiriesAdmin(page, pageSize, filter)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
}
|
||||
if inquiries == nil {
|
||||
utils.Success(c, []interface{}{})
|
||||
} else {
|
||||
utils.Success(c, inquiries)
|
||||
inquiries = []models.Inquiry{}
|
||||
}
|
||||
utils.Success(c, gin.H{
|
||||
"list": inquiries,
|
||||
"total": total,
|
||||
"page": page,
|
||||
"size": pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
// AdminUpdateInquiryStatus 更新咨询状态
|
||||
|
||||
@@ -235,13 +235,34 @@ func AdminGetPosts(c *gin.Context) {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
// 获取搜索关键词
|
||||
keyword := c.Query("keyword")
|
||||
if keyword == "" {
|
||||
keyword = c.Query("q") // 兼容 q 参数
|
||||
// 获取搜索与筛选参数
|
||||
filter := repositories.AdminPostFilter{
|
||||
Keyword: c.Query("keyword"),
|
||||
}
|
||||
if filter.Keyword == "" {
|
||||
filter.Keyword = c.Query("q")
|
||||
}
|
||||
if categoryIDStr := c.Query("categoryId"); categoryIDStr != "" {
|
||||
if id, err := strconv.ParseUint(categoryIDStr, 10, 32); err == nil {
|
||||
filter.CategoryID = uint(id)
|
||||
}
|
||||
}
|
||||
if columnIDStr := c.Query("columnId"); columnIDStr != "" {
|
||||
if id, err := strconv.ParseUint(columnIDStr, 10, 32); err == nil {
|
||||
filter.ColumnID = uint(id)
|
||||
}
|
||||
}
|
||||
if pubStr := c.Query("isPublished"); pubStr != "" {
|
||||
if v, err := strconv.Atoi(pubStr); err == nil {
|
||||
filter.IsPublished = v
|
||||
}
|
||||
} else {
|
||||
filter.IsPublished = -1
|
||||
}
|
||||
filter.StartDate = c.Query("startDate")
|
||||
filter.EndDate = c.Query("endDate")
|
||||
|
||||
posts, total, err := repositories.GetAllPosts(page, pageSize, keyword)
|
||||
posts, total, err := repositories.GetAllPosts(page, pageSize, filter)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
@@ -270,6 +291,11 @@ func AdminCreatePost(c *gin.Context) {
|
||||
}
|
||||
|
||||
// 创建文章
|
||||
if userID, ok := c.Get("userID"); ok {
|
||||
if uid, ok := userID.(uint); ok {
|
||||
post.UserID = uid
|
||||
}
|
||||
}
|
||||
if err := repositories.CreatePost(&post); err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
|
||||
@@ -80,7 +80,16 @@ func AdminGetSnippets(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
|
||||
|
||||
snippets, total, err := repositories.GetAdminSnippets(page, pageSize)
|
||||
filter := repositories.AdminSnippetFilter{
|
||||
Keyword: c.Query("keyword"),
|
||||
}
|
||||
if typeIDStr := c.Query("codeTypeId"); typeIDStr != "" {
|
||||
if id, err := strconv.ParseUint(typeIDStr, 10, 32); err == nil {
|
||||
filter.CodeTypeID = uint(id)
|
||||
}
|
||||
}
|
||||
|
||||
snippets, total, err := repositories.GetAdminSnippets(page, pageSize, filter)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
|
||||
@@ -19,8 +19,15 @@ func AdminGetUsers(c *gin.Context) {
|
||||
if keyword == "" {
|
||||
keyword = c.Query("q")
|
||||
}
|
||||
role := c.Query("role")
|
||||
isActive := -1
|
||||
if s := c.Query("isActive"); s != "" {
|
||||
if v, err := strconv.Atoi(s); err == nil {
|
||||
isActive = v
|
||||
}
|
||||
}
|
||||
|
||||
users, total, err := repositories.GetUsers(page, pageSize, keyword)
|
||||
users, total, err := repositories.GetUsers(page, pageSize, keyword, role, isActive)
|
||||
if err != nil {
|
||||
utils.ServerError(c, err)
|
||||
return
|
||||
|
||||
@@ -24,6 +24,8 @@ func main() {
|
||||
// repositories.MigrateToBigInt() // 已禁用自动迁移
|
||||
repositories.MigratePostHistoryFields()
|
||||
repositories.MigratePostSnippets()
|
||||
repositories.MigratePostUserID()
|
||||
repositories.MigrateUserAvatar()
|
||||
|
||||
// 初始化ip2region (如果文件不存在,将降级为普通IP记录)
|
||||
// 函数会自动从环境变量或可执行文件目录查找 ip2region.xdb
|
||||
|
||||
@@ -20,6 +20,7 @@ type Post struct {
|
||||
Content string `json:"content" gorm:"column:content;type:text"`
|
||||
ReadCount uint `json:"readCount" gorm:"column:read_count;default:0"`
|
||||
IsPublished int `json:"isPublished" gorm:"column:is_published;default:0"` // 0: draft, 1: published
|
||||
UserID uint `json:"userId" gorm:"column:user_id;default:0"`
|
||||
Tags []Tag `json:"tags" gorm:"many2many:post_tags;joinForeignKey:post_id;joinReferences:tag_id"`
|
||||
CreatedAt int64 `json:"createdAt" gorm:"column:created_at"`
|
||||
UpdatedAt int64 `json:"updatedAt" gorm:"column:updated_at"`
|
||||
@@ -68,6 +69,10 @@ type PostResponse struct {
|
||||
Tags []Tag `json:"tags,omitempty"`
|
||||
IsPublished int `json:"isPublished,omitempty"`
|
||||
ReadCount uint `json:"readCount,omitempty"`
|
||||
UserID uint `json:"userId,omitempty"`
|
||||
UserName string `json:"userName,omitempty"`
|
||||
UserAvatar string `json:"userAvatar,omitempty"`
|
||||
UserEmail string `json:"userEmail,omitempty"`
|
||||
Snippets []SnippetBrief `json:"snippets,omitempty"`
|
||||
}
|
||||
|
||||
@@ -133,6 +138,10 @@ type PostHistoryResponse struct {
|
||||
IsPublished int `json:"isPublished"`
|
||||
ModifiedBy uint `json:"modifiedBy"`
|
||||
ModifiedByName string `json:"modifiedByName,omitempty"`
|
||||
UserID uint `json:"userId"`
|
||||
UserName string `json:"userName,omitempty"`
|
||||
UserAvatar string `json:"userAvatar,omitempty"`
|
||||
UserEmail string `json:"userEmail,omitempty"`
|
||||
ColumnName string `json:"columnName,omitempty"`
|
||||
TagNames []string `json:"tagNames,omitempty"`
|
||||
ModifiedAt string `json:"modifiedAt"`
|
||||
|
||||
@@ -11,6 +11,7 @@ type User struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;column:id"`
|
||||
Username string `json:"username" gorm:"column:username;uniqueIndex;not null"`
|
||||
Email string `json:"email" gorm:"column:email"`
|
||||
Avatar string `json:"avatar" gorm:"column:avatar"`
|
||||
Password string `json:"password,omitempty" gorm:"-"` // Virtual field for input
|
||||
PasswordHash string `json:"-" gorm:"column:password_hash"`
|
||||
RoleID uint `json:"roleId" gorm:"column:role_id"`
|
||||
@@ -52,6 +53,7 @@ type UserResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Avatar string `json:"avatar,omitempty"`
|
||||
RoleID uint `json:"roleId"`
|
||||
Role string `json:"role"`
|
||||
IsActive int `json:"isActive"`
|
||||
|
||||
@@ -312,6 +312,7 @@ CREATE TABLE `posts` (
|
||||
`content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '文章内容',
|
||||
`read_count` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '阅读量',
|
||||
`is_published` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否已发布(0:草稿,1:已发布)',
|
||||
`user_id` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建者用户ID',
|
||||
`deleted_at` bigint NOT NULL DEFAULT 0,
|
||||
`created_at` bigint NOT NULL DEFAULT 0,
|
||||
`updated_at` bigint NOT NULL DEFAULT 0,
|
||||
@@ -319,6 +320,7 @@ CREATE TABLE `posts` (
|
||||
INDEX `idx_category_id`(`category_id` ASC) USING BTREE COMMENT '按分类查询索引',
|
||||
INDEX `idx_column_id`(`column_id` ASC) USING BTREE COMMENT '按专栏查询索引',
|
||||
INDEX `idx_is_published`(`is_published` ASC) USING BTREE COMMENT '按发布状态查询索引',
|
||||
INDEX `idx_user_id`(`user_id` ASC) USING BTREE,
|
||||
INDEX `idx_original_id`(`original_id` ASC) USING BTREE,
|
||||
FULLTEXT INDEX `idx_title_content`(`title`, `content`) COMMENT '标题和内容全文索引,用于搜索'
|
||||
) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '博客文章表(新结构)' ROW_FORMAT = DYNAMIC;
|
||||
@@ -478,6 +480,7 @@ CREATE TABLE `users` (
|
||||
`id` bigint UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
|
||||
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '用户名',
|
||||
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '邮箱地址',
|
||||
`avatar` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '头像URL',
|
||||
`password_hash` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '密码哈希值',
|
||||
`role_id` bigint UNSIGNED NULL DEFAULT NULL COMMENT '角色ID',
|
||||
`role` enum('admin','editor','viewer') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT 'viewer' COMMENT '用户角色(兼容旧版)',
|
||||
|
||||
@@ -20,7 +20,7 @@ func CreateAttachment(attachment *models.Attachment) error {
|
||||
}
|
||||
|
||||
// GetAttachments 获取附件列表(分页)
|
||||
func GetAttachments(page, pageSize int, categoryID *uint, fileType string) ([]models.Attachment, int64, error) {
|
||||
func GetAttachments(page, pageSize int, categoryID *uint, fileType, keyword string) ([]models.Attachment, int64, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var attachments []models.Attachment
|
||||
@@ -38,6 +38,11 @@ func GetAttachments(page, pageSize int, categoryID *uint, fileType string) ([]mo
|
||||
query = query.Where("file_type = ?", fileType)
|
||||
}
|
||||
|
||||
if keyword != "" {
|
||||
like := "%" + keyword + "%"
|
||||
query = query.Where("original_name LIKE ?", like)
|
||||
}
|
||||
|
||||
// Count total
|
||||
err := query.Count(&total).Error
|
||||
if err != nil {
|
||||
|
||||
@@ -18,17 +18,73 @@ func CreateInquiry(inquiry *models.Inquiry) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetInquiries 获取咨询列表 (Admin)
|
||||
func GetInquiries() ([]models.Inquiry, error) {
|
||||
// AdminInquiryFilter 咨询列表筛选
|
||||
type AdminInquiryFilter struct {
|
||||
Keyword string
|
||||
Status int // -1 表示全部
|
||||
StartDate string
|
||||
EndDate string
|
||||
}
|
||||
|
||||
// GetInquiriesAdmin 获取咨询列表 (Admin,分页)
|
||||
func GetInquiriesAdmin(page, pageSize int, filter AdminInquiryFilter) ([]models.Inquiry, int64, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
var inquiries []models.Inquiry
|
||||
err := config.DB.Model(&models.Inquiry{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Order("created_at DESC").
|
||||
Find(&inquiries).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var total int64
|
||||
|
||||
query := config.DB.Model(&models.Inquiry{}).
|
||||
Where("deleted_at = ?", 0)
|
||||
|
||||
if filter.Keyword != "" {
|
||||
like := "%" + filter.Keyword + "%"
|
||||
query = query.Where("(name LIKE ? OR company LIKE ? OR contact_value LIKE ? OR description LIKE ?)",
|
||||
like, like, like, like)
|
||||
}
|
||||
return inquiries, nil
|
||||
if filter.Status >= 0 {
|
||||
query = query.Where("status = ?", filter.Status)
|
||||
}
|
||||
if filter.StartDate != "" {
|
||||
if ts, err := parseInquiryDateStart(filter.StartDate); err == nil {
|
||||
query = query.Where("created_at >= ?", ts)
|
||||
}
|
||||
}
|
||||
if filter.EndDate != "" {
|
||||
if ts, err := parseInquiryDateEnd(filter.EndDate); err == nil {
|
||||
query = query.Where("created_at <= ?", ts)
|
||||
}
|
||||
}
|
||||
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
err := query.Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Find(&inquiries).Error
|
||||
return inquiries, total, err
|
||||
}
|
||||
|
||||
func parseInquiryDateStart(dateStr string) (int64, error) {
|
||||
t, err := time.ParseInLocation("2006-01-02", dateStr, time.Local)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return t.Unix(), nil
|
||||
}
|
||||
|
||||
func parseInquiryDateEnd(dateStr string) (int64, error) {
|
||||
t, err := time.ParseInLocation("2006-01-02", dateStr, time.Local)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return t.Add(24*time.Hour - time.Second).Unix(), nil
|
||||
}
|
||||
|
||||
// GetInquiries 获取咨询列表 (Admin) — 兼容旧调用
|
||||
func GetInquiries() ([]models.Inquiry, error) {
|
||||
list, _, err := GetInquiriesAdmin(1, 10000, AdminInquiryFilter{Status: -1})
|
||||
return list, err
|
||||
}
|
||||
|
||||
// GetInquiryCount 获取咨询总数
|
||||
|
||||
@@ -156,3 +156,22 @@ func MigratePostSnippets() {
|
||||
INDEX idx_sort_order (sort_order)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='文章代码片段关联表'`)
|
||||
}
|
||||
|
||||
// MigratePostUserID adds user_id to posts if missing.
|
||||
func MigratePostUserID() {
|
||||
if columnExists("posts", "user_id") {
|
||||
return
|
||||
}
|
||||
log.Printf("Adding posts.user_id...")
|
||||
execSQL("ALTER TABLE `posts` ADD COLUMN `user_id` INT UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建者用户ID' AFTER `is_published`")
|
||||
execSQL("ALTER TABLE `posts` ADD INDEX `idx_user_id` (`user_id`)")
|
||||
}
|
||||
|
||||
// MigrateUserAvatar adds avatar to users if missing.
|
||||
func MigrateUserAvatar() {
|
||||
if columnExists("users", "avatar") {
|
||||
return
|
||||
}
|
||||
log.Printf("Adding users.avatar...")
|
||||
execSQL("ALTER TABLE `users` ADD COLUMN `avatar` VARCHAR(500) NULL DEFAULT NULL COMMENT '头像URL' AFTER `email`")
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ func GetPostByID(id uint) (*models.Post, error) {
|
||||
var post models.Post
|
||||
// 显式选择所有字段,确保 content 字段被加载
|
||||
err := config.DB.Model(&models.Post{}).
|
||||
Select("id", "title", "category_id", "column_id", "excerpt", "content", "read_count", "is_published", "created_at", "updated_at", "deleted_at").
|
||||
Select("id", "title", "category_id", "column_id", "excerpt", "content", "read_count", "is_published", "user_id", "created_at", "updated_at", "deleted_at").
|
||||
Preload("Category").
|
||||
Preload("Column").
|
||||
Preload("Tags").
|
||||
@@ -107,25 +107,50 @@ func IncrementReadCount(id uint) error {
|
||||
UpdateColumn("read_count", gorm.Expr("read_count + ?", 1)).Error
|
||||
}
|
||||
|
||||
// GetAllPosts 获取所有博客文章(包括未发布的,后台用,支持搜索)
|
||||
func GetAllPosts(page, pageSize int, keyword string) ([]models.Post, int64, error) {
|
||||
// GetAllPosts 获取所有博客文章(包括未发布的,后台用,支持搜索与筛选)
|
||||
type AdminPostFilter struct {
|
||||
Keyword string
|
||||
CategoryID uint
|
||||
ColumnID uint
|
||||
IsPublished int // -1 表示全部
|
||||
StartDate string
|
||||
EndDate string
|
||||
}
|
||||
|
||||
func GetAllPosts(page, pageSize int, filter AdminPostFilter) ([]models.Post, int64, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var posts []models.Post
|
||||
var total int64
|
||||
|
||||
// 构建查询
|
||||
query := config.DB.Model(&models.Post{}).
|
||||
Where("deleted_at = ?", 0)
|
||||
|
||||
// 如果有关键词,添加搜索条件
|
||||
if keyword != "" {
|
||||
likeKeyword := "%" + keyword + "%"
|
||||
if filter.Keyword != "" {
|
||||
likeKeyword := "%" + filter.Keyword + "%"
|
||||
query = query.Where("(MATCH(title, content) AGAINST(? IN BOOLEAN MODE) OR title LIKE ? OR content LIKE ?)",
|
||||
keyword, likeKeyword, likeKeyword)
|
||||
filter.Keyword, likeKeyword, likeKeyword)
|
||||
}
|
||||
if filter.CategoryID > 0 {
|
||||
query = query.Where("category_id = ?", filter.CategoryID)
|
||||
}
|
||||
if filter.ColumnID > 0 {
|
||||
query = query.Where("column_id = ?", filter.ColumnID)
|
||||
}
|
||||
if filter.IsPublished >= 0 {
|
||||
query = query.Where("is_published = ?", filter.IsPublished)
|
||||
}
|
||||
if filter.StartDate != "" {
|
||||
if ts, err := parseDateStart(filter.StartDate); err == nil {
|
||||
query = query.Where("created_at >= ?", ts)
|
||||
}
|
||||
}
|
||||
if filter.EndDate != "" {
|
||||
if ts, err := parseDateEnd(filter.EndDate); err == nil {
|
||||
query = query.Where("created_at <= ?", ts)
|
||||
}
|
||||
}
|
||||
|
||||
// Count total
|
||||
err := query.Count(&total).Error
|
||||
if err != nil {
|
||||
log.Printf("Error counting posts: %v", err)
|
||||
@@ -365,10 +390,10 @@ func BuildPostResponse(post *models.Post, includeContent bool) *models.PostRespo
|
||||
colSlug := ""
|
||||
if post.Column != nil {
|
||||
colName = post.Column.Name
|
||||
// 如果 Column 有 Slug 字段,可以在这里添加
|
||||
// colSlug = post.Column.Slug
|
||||
}
|
||||
|
||||
userName, userAvatar, userEmail := lookupUserBrief(post.UserID)
|
||||
|
||||
response := &models.PostResponse{
|
||||
ID: post.ID,
|
||||
Title: post.Title,
|
||||
@@ -383,6 +408,10 @@ func BuildPostResponse(post *models.Post, includeContent bool) *models.PostRespo
|
||||
Tags: post.Tags,
|
||||
IsPublished: post.IsPublished,
|
||||
ReadCount: post.ReadCount,
|
||||
UserID: post.UserID,
|
||||
UserName: userName,
|
||||
UserAvatar: userAvatar,
|
||||
UserEmail: userEmail,
|
||||
}
|
||||
|
||||
if includeContent {
|
||||
@@ -562,6 +591,7 @@ func GetPostHistoryByVersion(postID uint, version uint) (*models.PostHistory, er
|
||||
|
||||
// BuildPostHistoryResponse 构建历史记录响应
|
||||
func BuildPostHistoryResponse(h *models.PostHistory, includeFull bool) *models.PostHistoryResponse {
|
||||
opName, opAvatar, opEmail := lookupUserBrief(h.ModifiedBy)
|
||||
resp := &models.PostHistoryResponse{
|
||||
ID: h.ID,
|
||||
PostID: h.PostID,
|
||||
@@ -576,7 +606,11 @@ func BuildPostHistoryResponse(h *models.PostHistory, includeFull bool) *models.P
|
||||
Date: time.Unix(h.CreatedAt, 0).Format("2006-01-02"),
|
||||
IsPublished: h.IsPublished,
|
||||
ModifiedBy: h.ModifiedBy,
|
||||
ModifiedByName: lookupUsername(h.ModifiedBy),
|
||||
ModifiedByName: opName,
|
||||
UserID: h.ModifiedBy,
|
||||
UserName: opName,
|
||||
UserAvatar: opAvatar,
|
||||
UserEmail: opEmail,
|
||||
ModifiedAt: time.Unix(h.ModifiedAt, 0).Format("2006-01-02 15:04:05"),
|
||||
CreatedAt: time.Unix(h.CreatedAt, 0).Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
@@ -620,13 +654,24 @@ func lookupTagNames(ids []uint) []string {
|
||||
return names
|
||||
}
|
||||
|
||||
func lookupUsername(id uint) string {
|
||||
func lookupUserBrief(id uint) (username, avatar, email string) {
|
||||
if id == 0 {
|
||||
return ""
|
||||
return "", "", ""
|
||||
}
|
||||
var username string
|
||||
config.DB.Model(&models.User{}).Where("id = ?", id).Pluck("username", &username)
|
||||
return username
|
||||
var user models.User
|
||||
err := config.DB.Model(&models.User{}).
|
||||
Select("username", "avatar", "email").
|
||||
Where("id = ? AND deleted_at = ?", id, 0).
|
||||
First(&user).Error
|
||||
if err != nil {
|
||||
return "", "", ""
|
||||
}
|
||||
return user.Username, user.Avatar, user.Email
|
||||
}
|
||||
|
||||
func lookupUsername(id uint) string {
|
||||
name, _, _ := lookupUserBrief(id)
|
||||
return name
|
||||
}
|
||||
|
||||
func formatPublishedLabel(v int) string {
|
||||
@@ -857,3 +902,19 @@ func GetRecommendedPostsByIP(currentPostID uint, userIP string, limit int) ([]mo
|
||||
|
||||
return sortedPosts, nil
|
||||
}
|
||||
|
||||
func parseDateStart(dateStr string) (int64, error) {
|
||||
t, err := time.ParseInLocation("2006-01-02", dateStr, time.Local)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return t.Unix(), nil
|
||||
}
|
||||
|
||||
func parseDateEnd(dateStr string) (int64, error) {
|
||||
t, err := time.ParseInLocation("2006-01-02", dateStr, time.Local)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return t.Add(24*time.Hour - time.Second).Unix(), nil
|
||||
}
|
||||
|
||||
@@ -133,25 +133,38 @@ func DeleteSnippet(id uint) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AdminSnippetFilter 后台代码片段筛选
|
||||
type AdminSnippetFilter struct {
|
||||
Keyword string
|
||||
CodeTypeID uint
|
||||
}
|
||||
|
||||
// GetAdminSnippets 获取后台代码片段列表 (分页)
|
||||
func GetAdminSnippets(page, pageSize int) ([]models.Snippet, int, error) {
|
||||
func GetAdminSnippets(page, pageSize int, filter AdminSnippetFilter) ([]models.Snippet, int, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
var snippets []models.Snippet
|
||||
var total int64
|
||||
|
||||
// 获取总数
|
||||
err := config.DB.Model(&models.Snippet{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
Count(&total).Error
|
||||
query := config.DB.Model(&models.Snippet{}).
|
||||
Where("deleted_at = ?", 0)
|
||||
|
||||
if filter.Keyword != "" {
|
||||
like := "%" + filter.Keyword + "%"
|
||||
query = query.Where("title LIKE ?", like)
|
||||
}
|
||||
if filter.CodeTypeID > 0 {
|
||||
query = query.Where("code_type_id = ?", filter.CodeTypeID)
|
||||
}
|
||||
|
||||
err := query.Count(&total).Error
|
||||
if err != nil {
|
||||
log.Printf("Error getting snippet count: %v", err)
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 获取列表
|
||||
err = config.DB.Model(&models.Snippet{}).
|
||||
Where("deleted_at = ?", 0).
|
||||
err = query.
|
||||
Order("created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
|
||||
@@ -49,17 +49,24 @@ func GetUserByID(id uint) (*models.User, error) {
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetUsers 获取所有用户 (分页,支持 keyword 搜索)
|
||||
func GetUsers(page, pageSize int, keyword string) ([]models.User, int, error) {
|
||||
// GetUsers 获取所有用户 (分页,支持 keyword / role / isActive 搜索)
|
||||
func GetUsers(page, pageSize int, keyword, role string, isActive int) ([]models.User, int, error) {
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
query := config.DB.Model(&models.User{}).
|
||||
Where("users.deleted_at = ?", 0)
|
||||
Where("users.deleted_at = ?", 0).
|
||||
Joins("LEFT JOIN roles ON users.role_id = roles.id AND roles.deleted_at = 0")
|
||||
|
||||
if keyword != "" {
|
||||
like := "%" + keyword + "%"
|
||||
query = query.Where("users.username LIKE ? OR users.email LIKE ?", like, like)
|
||||
}
|
||||
if role != "" {
|
||||
query = query.Where("COALESCE(roles.name, users.role) = ?", role)
|
||||
}
|
||||
if isActive >= 0 {
|
||||
query = query.Where("users.is_active = ?", isActive)
|
||||
}
|
||||
|
||||
var total int64
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
@@ -67,10 +74,23 @@ func GetUsers(page, pageSize int, keyword string) ([]models.User, int, error) {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
listQuery := config.DB.Model(&models.User{}).
|
||||
Where("users.deleted_at = ?", 0).
|
||||
Joins("LEFT JOIN roles ON users.role_id = roles.id AND roles.deleted_at = 0")
|
||||
if keyword != "" {
|
||||
like := "%" + keyword + "%"
|
||||
listQuery = listQuery.Where("users.username LIKE ? OR users.email LIKE ?", like, like)
|
||||
}
|
||||
if role != "" {
|
||||
listQuery = listQuery.Where("COALESCE(roles.name, users.role) = ?", role)
|
||||
}
|
||||
if isActive >= 0 {
|
||||
listQuery = listQuery.Where("users.is_active = ?", isActive)
|
||||
}
|
||||
|
||||
var users []models.User
|
||||
err := query.
|
||||
err := listQuery.
|
||||
Select("users.*, COALESCE(roles.name, users.role) as role").
|
||||
Joins("LEFT JOIN roles ON users.role_id = roles.id").
|
||||
Order("users.created_at DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
@@ -116,6 +136,7 @@ func UpdateUser(user *models.User) error {
|
||||
updates := map[string]interface{}{
|
||||
"username": user.Username,
|
||||
"email": user.Email,
|
||||
"avatar": user.Avatar,
|
||||
"role": user.Role,
|
||||
"is_active": user.IsActive,
|
||||
"updated_at": time.Now().Unix(),
|
||||
@@ -187,6 +208,7 @@ func BuildUserResponse(user *models.User) *models.UserResponse {
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
Avatar: user.Avatar,
|
||||
RoleID: user.RoleID,
|
||||
Role: user.Role,
|
||||
IsActive: user.IsActive,
|
||||
|
||||
Reference in New Issue
Block a user